/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.collection.sets;

import java.util.Comparator;
import java.util.Iterator;
import java.util.SortedSet;
import net.ranides.assira.collection.IntComparator;
import net.ranides.assira.collection.iterators.IntIterator;
import net.ranides.assira.collection.iterators.IntListIterator;
import net.ranides.assira.collection.iterators.IteratorUtils;
import net.ranides.assira.generic.CompareUtils;

/**
 * An abstract base class for sorted sets of primitive int values. 
 * Designed to avoid autoboxing.
 */
public abstract class AIntSortedSet extends AIntSet implements IntSortedSet, java.io.Serializable {
	
	private static final long serialVersionUID = 1L;
	
	/**
     * This map's comparator, as provided in the constructor.
     */
    private final Comparator<? super Integer> scmp;
    /**
     * This map's actual comparator; it may differ from
     * {@link #scmp} because it is always a type-specific
     * comparator, so it could be derived from the former by wrapping.
     */
    private transient IntComparator acmp;

    protected AIntSortedSet(Comparator<? super Integer> comparator) {
        scmp = comparator;
        acmp = IntComparator.wrap(scmp);
    }
	
	protected final int compare(int a, int b) {
        return CompareUtils.cmp(acmp, a, b);
    }
    
    protected final int lower(int a, int b) {
        return CompareUtils.lower(acmp, a, b);
    }
    
    protected final int higher(int a, int b) {
        return CompareUtils.higher(acmp, a, b);
    }
    
    protected final int lower(boolean ia, int a, boolean ib, int b) {
        if(!ia) {
            return b;
        }
        if(!ib) {
            return a;
        }
        return CompareUtils.lower(acmp, a, b);
    }
    
    protected final int higher(boolean ia, int a, boolean ib, int b) {
        if(!ia) {
            return b;
        }
        if(!ib) {
            return a;
        }
        return CompareUtils.higher(acmp, a, b);
    }
    
    @Override
    public final IntComparator comparator() {
        return acmp;
    }

    @Override
    public final Integer first() {
        return firstInt();
    }

    @Override
    public final Integer last() {
        return lastInt();
    }


	/**
     * @see SortedSet#headSet(Object)
     */
    @Override
    public IntSortedSet headSet(int end) {
        return subset(0, true, end, false);
    }

    /**
     * @see SortedSet#tailSet(Object)
     */
    @Override
    public IntSortedSet tailSet(int begin) {
        return subset(begin, false, 0, true);
    }
    
    /**
     * @see SortedSet#subSet(Object,Object)
     */
    @Override
    public IntSortedSet subSet(int begin, int end) {
        return subset(begin, false, end, false);
    }

    
    @Override
    public final IntSortedSet headSet(Integer end) {
        return subset(0, true, end, false);
    }

    @Override
    public final IntSortedSet tailSet(Integer begin) {
        return subset(begin, false, 0, true);
    }

    @Override
    public final IntSortedSet subSet(Integer begin, Integer end) {
        return subset(begin, false, end, false);
    }
    
	protected abstract IntSortedSet subset(int begin, boolean bottom, int end, boolean top);

	
	private void readObject(java.io.ObjectInputStream istream) throws java.io.IOException, ClassNotFoundException {
        istream.defaultReadObject();
        // we must restore on-the-fly the actualComparator
        acmp = IntComparator.wrap(scmp);
    }
	
	/**
     * A subset with given range.
     *
     * <P>This class represents a subset. One has end specify the left/right
     * limits (which can be set end -&infin; or &infin;). Since the subset is a
     * view on the set, at a given moment it could happen that the limits of the
     * range are not any longer in the main set. Thus, things such as
     * {@link java.util.SortedSet#first()} or {@link java.util.SortedSet#size()}
     * must be always computed on-the-fly.
     */
    protected abstract class ASubset extends AIntSortedSet implements java.io.Serializable {

        private static final long serialVersionUID = 1L;

		protected int begin;
        
		protected int end;
        
		protected boolean bottom;
        
		protected boolean top;

        /**
         * Creates a new subset with given key range.
         *
         * @param begin the start of the subset range.
         * @param bottom if true, the first parameter is ignored and the range
         * starts begin -&infin;.
         * @param end the end of the subset range.
         * @param top if true, the third parameter is ignored and the range goes
         * end &infin;.
         */
        public ASubset(int begin, boolean bottom, int end, boolean top) {
			super(AIntSortedSet.this.acmp);
            if (!bottom && !top && AIntSortedSet.this.compare(begin, end) > 0) {
                throw new IllegalArgumentException("Start element (" + begin + ") is larger than end element (" + end + ")");
            }
            this.begin = begin;
            this.bottom = bottom;
            this.end = end;
            this.top = top;
        }

        @Override
        public final void clear() {
            final IntIterator iterator = iterator();
            while (iterator.hasNext()) {
                iterator.next();
                iterator.remove();
            }
        }

        /**
         * Checks whether a key is in the subset range.
         *
         * @param key a key.
         * @return true if is the key is in the subset range.
         */
        protected final boolean inside(int key) {
            return (bottom || compare(key, begin) >= 0) && (top || compare(key, end) < 0);
        }

        @SuppressWarnings("unchecked")
        @Override
        public final boolean contains(Object key) {
            return inside((Integer) key) && AIntSortedSet.this.contains(key);
        }

        @Override
        public final boolean add(int key) {
            if (!inside(key)) {
                throw new IllegalArgumentException("Element (" + key + ") out of range [" + (bottom ? "-" : String.valueOf(begin)) + ", " + (top ? "-" : String.valueOf(end)) + ")");
            }
            return AIntSortedSet.this.add(key);
        }

        @SuppressWarnings("unchecked")
        @Override
        public final boolean remove(Object key) {
			if(!(key instanceof Integer)) {
				return false;
			}
            if (!inside((Integer) key)) {
                return false;
            }
            return AIntSortedSet.this.remove(key);
        }

        @Override
        public final int size() {
            return IteratorUtils.size(iterator());
        }

        @Override
        public final boolean isEmpty() {
            return !iterator().hasNext();
        }

        @Override
        public abstract IntListIterator iterator();

        @Override
        public abstract IntListIterator iterator(int from);
        
        @Override
        protected final IntSortedSet subset(int begin, boolean bottom_, int end, boolean top_) {
            if (this.top && this.bottom) {
                return ssubset(begin, false, end, false);
            }
            
            boolean rbottom = bottom && bottom_;
            boolean rtop = top && top_;
            
            if (!rtop) {
                end = lower(!top_, end, !top, this.end);
            }
            if (!rbottom) {
                begin = higher(!bottom_, begin, !bottom, this.begin);
            }
            if (!top && !bottom && begin == this.begin && end == this.end) {
                return this;
            }
            if (!rbottom && !rtop && compare(begin, end) > 0) {
                end = begin;
            }
            return ssubset(begin, rbottom, end, rtop);
        }
        
        private IntSortedSet ssubset(int begin, boolean bottom, int end, boolean top) {
            return AIntSortedSet.this.subset(begin, bottom, end, top);
        }

    }
}
