/*
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
 *
 * 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 org.jivesoftware.openfire.resultsetmanager;

import java.util.*;

/**
 * A result set representation as described in XEP-0059. Note that this result
 * 'set' actually makes use of a List implementations, as the Java Set
 * definition disallows duplicate elements, while the List definition supplies
 * most of the required indexing operations.
 * 
 * This ResultSet implementation loads all all results from the set into memory,
 * which might be undesirable for very large sets, or for sets where the
 * retrieval of a result is an expensive operation. sets.
 * 
 * As most methods are backed by the {@link List#subList(int, int)} method,
 * non-structural changes in the returned lists are reflected in the ResultSet,
 * and vice-versa.
 * 
 * @author Guus der Kinderen, guus@nimbuzz.com
 * 
 * @param <E>
 *            Each result set should be a collection of instances of the exact
 *            same class. This class must implement the {@link Result}
 *            interface.
 * @see java.util.List#subList(int, int)
 * @deprecated Replaced by {@link org.xmpp.resultsetmanagement.ResultSetImpl}
 */
/*
 * TODO: do we want changes to the returned Lists of methods in this class be
 * applied to the content of the ResultSet itself? Currently, because of the
 * usage of java.util.List#subList(int, int), it does. I'm thinking a
 * immodifiable solution would cause less problems. -Guus
 */
@Deprecated
public class ResultSetImpl<E extends Result> extends ResultSet<E> {

    /**
     * A list of all results in this ResultSet
     */
    public final List<E> resultList;

    /**
     * A mapping of the UIDs of all results in resultList, to the index of those
     * entries in that list.
     */
    public final Map<String, Integer> uidToIndex;

    /**
     * Creates a new Result Set instance, based on a collection of Result
     * implementing objects. The collection should contain elements of the exact
     * same class only, and cannot contain 'null' elements.
     * 
     * The order that's being used in the new ResultSet instance is the same
     * order in which {@link Collection#iterator()} iterates over the
     * collection.
     * 
     * Note that this constructor throws an IllegalArgumentException if the
     * Collection that is provided contains Results that have duplicate UIDs.
     * 
     * @param results
     *            The collection of Results that make up this result set.
     */
    public ResultSetImpl(Collection<E> results) {
        this(results, null);
    }

    /**
     * Creates a new Result Set instance, based on a collection of Result
     * implementing objects. The collection should contain elements of the exact
     * same class only, and cannot contain 'null' elements.
     * 
     * The order that's being used in the new ResultSet instance is defined by
     * the supplied Comparator class.
     * 
     * Note that this constructor throws an IllegalArgumentException if the
     * Collection that is provided contains Results that have duplicate UIDs.
     * 
     * @param results
     *            The collection of Results that make up this result set.
     * @param comparator
     *            The Comparator that defines the order of the Results in this
     *            result set.
     */
    public ResultSetImpl(Collection<E> results, Comparator<E> comparator) {
        if (results == null) {
            throw new NullPointerException("Argument 'results' cannot be null.");
        }

        final int size = results.size();
        resultList = new ArrayList<>(size);
        uidToIndex = new Hashtable<>(size);

        // sort the collection, if need be.
        List<E> sortedResults = null;
        if (comparator != null) {
            sortedResults = new ArrayList<>(results);
            Collections.sort(sortedResults, comparator);
        }

        int index = 0;
        // iterate over either the sorted or unsorted collection
        for (final E result : (sortedResults != null ? sortedResults : results)) {
            if (result == null) {
                throw new NullPointerException(
                        "The result set must not contain 'null' elements.");
            }

            final String uid = result.getUID();
            if (uidToIndex.containsKey(uid)) {
                throw new IllegalArgumentException(
                        "The result set can not contain elements that have the same UID.");
            }

            resultList.add(result);
            uidToIndex.put(uid, index);
            index++;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.buzzaa.xmpp.resultsetmanager.ResultSet#size()
     */
    @Override
    public int size() {
        return resultList.size();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.buzzaa.xmpp.resultsetmanager.ResultSet#getAfter(E, int)
     */
    @Override
    public List<E> getAfter(String uid, int maxAmount) {
        if (uid == null || uid.length() == 0) {
            throw new NullPointerException("Argument 'uid' cannot be null or an empty String.");
        }

        if (maxAmount < 1) {
            throw new IllegalArgumentException(
                    "Argument 'maxAmount' must be a integer higher than zero.");
        }

        // the result of this method is exclusive 'result'
        final int index = uidToIndex.get(uid) + 1;

        return get(index, maxAmount);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.buzzaa.xmpp.resultsetmanager.ResultSet#getBefore(E, int)
     */
    @Override
    public List<E> getBefore(String uid, int maxAmount) {
        if (uid == null || uid.length() == 0) {
            throw new NullPointerException("Argument 'uid' cannot be null or an empty String.");
        }

        if (maxAmount < 1) {
            throw new IllegalArgumentException(
                    "Argument 'maxAmount' must be a integer higher than zero.");
        }

        // the result of this method is exclusive 'result'
        final int indexOfLastElement = uidToIndex.get(uid);
        final int indexOfFirstElement = indexOfLastElement - maxAmount;

        if (indexOfFirstElement < 0) {
            return get(0, indexOfLastElement);
        }

        return get(indexOfFirstElement, maxAmount);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.buzzaa.xmpp.resultsetmanager.ResultSet#get(int)
     */
    @Override
    public E get(int index) {
        return resultList.get(index);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.buzzaa.xmpp.resultsetmanager.ResultSet#getFirst(int)
     */
    @Override
    public List<E> getFirst(int maxAmount) {
        if (maxAmount < 1) {
            throw new IllegalArgumentException(
                    "Argument 'maxAmount' must be a integer higher than zero.");
        }

        return get(0, maxAmount);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.buzzaa.xmpp.resultsetmanager.ResultSet#getLast(int)
     */
    @Override
    public List<E> getLast(int maxAmount) {
        if (maxAmount < 1) {
            throw new IllegalArgumentException(
                    "Argument 'maxAmount' must be a integer higher than zero.");
        }

        final int indexOfFirstElement = size() - maxAmount;

        if (indexOfFirstElement < 0) {
            return get(0, maxAmount);
        }

        return get(indexOfFirstElement, maxAmount);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.buzzaa.xmpp.resultsetmanager.ResultSet#get(int, int)
     */
    @Override
    public List<E> get(int fromIndex, int maxAmount) {
        if (fromIndex < 0) {
            throw new IllegalArgumentException(
                    "Argument 'fromIndex' must be zero or higher.");
        }

        if (maxAmount < 1) {
            throw new IllegalArgumentException(
                    "Argument 'maxAmount' must be a integer higher than zero.");
        }

        if (fromIndex >= size()) {
            return new ArrayList<>(0);
        }

        // calculate the last index to return, or return up to the end of last
        // index if 'amount' surpasses the list length.
        final int absoluteTo = fromIndex + maxAmount;
        final int toIndex = (absoluteTo > size() ? size() : absoluteTo);

        return resultList.subList(fromIndex, toIndex);
    }

    /*
     * (non-Javadoc)
     * @see org.jivesoftware.util.resultsetmanager.ResultSet#indexOf(java.lang.String)
     */
    @Override
    public int indexOf(String uid) {
        return uidToIndex.get(uid);
    }
}
