/*
 * Copyright 2012 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkTSet_DEFINED
#define SkTSet_DEFINED

#include "SkTSort.h"
#include "SkTDArray.h"
#include "SkTypes.h"

/** \class SkTSet<T>

    The SkTSet template class defines a set. Elements are additionally
    guaranteed to be sorted by their insertion order.
    Main operations supported now are: add, merge, find and contains.

    TSet<T> is mutable.
*/

// TODO: Add remove, intersect and difference operations.
// TODO: Add bench tests.
template <typename T> class SkTSet {
public:
    SkTSet() {
        fSetArray = SkNEW(SkTDArray<T>);
        fOrderedArray = SkNEW(SkTDArray<T>);
    }

    ~SkTSet() {
        SkASSERT(fSetArray);
        SkDELETE(fSetArray);
        SkASSERT(fOrderedArray);
        SkDELETE(fOrderedArray);
    }

    SkTSet(const SkTSet<T>& src) {
        this->fSetArray = SkNEW_ARGS(SkTDArray<T>, (*src.fSetArray));
        this->fOrderedArray = SkNEW_ARGS(SkTDArray<T>, (*src.fOrderedArray));
#ifdef SK_DEBUG
        validate();
#endif
    }

    SkTSet<T>& operator=(const SkTSet<T>& src) {
        *this->fSetArray = *src.fSetArray;
        *this->fOrderedArray = *src.fOrderedArray;
#ifdef SK_DEBUG
        validate();
#endif
        return *this;
    }

    /** Merges src elements into this, and returns the number of duplicates
     * found. Elements from src will retain their ordering and will be ordered
     * after the elements currently in this set.
     *
     * Implementation note: this uses a 2-stage merge to obtain O(n log n) time.
     * The first stage goes through src.fOrderedArray, checking if
     * this->contains() is false before adding to this.fOrderedArray.
     * The second stage does a standard sorted list merge on the fSetArrays.
     */
    int mergeInto(const SkTSet<T>& src) {
        SkASSERT(fSetArray);
        SkASSERT(fOrderedArray);

        // Do fOrderedArray merge.
        for (int i = 0; i < src.count(); ++i) {
            if (!contains((*src.fOrderedArray)[i])) {
                fOrderedArray->push((*src.fOrderedArray)[i]);
            }
        }

        // Do fSetArray merge.
        int duplicates = 0;

        SkTDArray<T>* fArrayNew = new SkTDArray<T>();
        fArrayNew->setReserve(fOrderedArray->count());
        int i = 0;
        int j = 0;

        while (i < fSetArray->count() && j < src.count()) {
            if ((*fSetArray)[i] < (*src.fSetArray)[j]) {
                fArrayNew->push((*fSetArray)[i]);
                i++;
            } else if ((*fSetArray)[i] > (*src.fSetArray)[j]) {
                fArrayNew->push((*src.fSetArray)[j]);
                j++;
            } else {
                duplicates++;
                j++; // Skip one of the duplicates.
            }
        }

        while (i < fSetArray->count()) {
            fArrayNew->push((*fSetArray)[i]);
            i++;
        }

        while (j < src.count()) {
            fArrayNew->push((*src.fSetArray)[j]);
            j++;
        }
        SkDELETE(fSetArray);
        fSetArray = fArrayNew;
        fArrayNew = NULL;

#ifdef SK_DEBUG
        validate();
#endif
        return duplicates;
    }

    /** Adds a new element into set and returns false if the element is already
     * in this set.
    */
    bool add(const T& elem) {
        SkASSERT(fSetArray);
        SkASSERT(fOrderedArray);

        int pos = 0;
        int i = find(elem, &pos);
        if (i >= 0) {
            return false;
        }
        *fSetArray->insert(pos) = elem;
        fOrderedArray->push(elem);
#ifdef SK_DEBUG
        validate();
#endif
        return true;
    }

    /** Returns true if this set is empty.
    */
    bool isEmpty() const {
        SkASSERT(fOrderedArray);
        SkASSERT(fSetArray);
        SkASSERT(fSetArray->isEmpty() == fOrderedArray->isEmpty());
        return fOrderedArray->isEmpty();
    }

    /** Return the number of elements in the set.
     */
    int count() const {
        SkASSERT(fOrderedArray);
        SkASSERT(fSetArray);
        SkASSERT(fSetArray->count() == fOrderedArray->count());
        return fOrderedArray->count();
    }

    /** Return the number of bytes in the set: count * sizeof(T).
     */
    size_t bytes() const {
        SkASSERT(fOrderedArray);
        return fOrderedArray->bytes();
    }

    /** Return the beginning of a set iterator.
     * Elements in the iterator will be sorted ascending.
     */
    const T*  begin() const {
        SkASSERT(fOrderedArray);
        return fOrderedArray->begin();
    }

    /** Return the end of a set iterator.
     */
    const T*  end() const {
        SkASSERT(fOrderedArray);
        return fOrderedArray->end();
    }

    const T&  operator[](int index) const {
        SkASSERT(fOrderedArray);
        return (*fOrderedArray)[index];
    }

    /** Resets the set (deletes memory and initiates an empty set).
     */
    void reset() {
        SkASSERT(fSetArray);
        SkASSERT(fOrderedArray);
        fSetArray->reset();
        fOrderedArray->reset();
    }

    /** Rewinds the set (preserves memory and initiates an empty set).
     */
    void rewind() {
        SkASSERT(fSetArray);
        SkASSERT(fOrderedArray);
        fSetArray->rewind();
        fOrderedArray->rewind();
    }

    /** Reserves memory for the set.
     */
    void setReserve(int reserve) {
        SkASSERT(fSetArray);
        SkASSERT(fOrderedArray);
        fSetArray->setReserve(reserve);
        fOrderedArray->setReserve(reserve);
    }

    /** Returns true if the array contains this element.
     */
    bool contains(const T& elem) const {
        SkASSERT(fSetArray);
        return (this->find(elem) >= 0);
    }

    /** Copies internal array to destination.
     */
    void copy(T* dst) const {
        SkASSERT(fOrderedArray);
        fOrderedArray->copyRange(dst, 0, fOrderedArray->count());
    }

    /** Returns a const reference to the internal vector.
     */
    const SkTDArray<T>& toArray() {
        SkASSERT(fOrderedArray);
        return *fOrderedArray;
    }

    /** Unref all elements in the set.
     */
    void unrefAll() {
        SkASSERT(fSetArray);
        SkASSERT(fOrderedArray);
        fOrderedArray->unrefAll();
        // Also reset the other array, as SkTDArray::unrefAll does an
        // implcit reset
        fSetArray->reset();
    }

    /** safeUnref all elements in the set.
     */
    void safeUnrefAll() {
        SkASSERT(fSetArray);
        SkASSERT(fOrderedArray);
        fOrderedArray->safeUnrefAll();
        // Also reset the other array, as SkTDArray::safeUnrefAll does an
        // implcit reset
        fSetArray->reset();
    }

#ifdef SK_DEBUG
    void validate() const {
        SkASSERT(fSetArray);
        SkASSERT(fOrderedArray);
        fSetArray->validate();
        fOrderedArray->validate();
        SkASSERT(isSorted() && !hasDuplicates() && arraysConsistent());
    }

    bool hasDuplicates() const {
        for (int i = 0; i < fSetArray->count() - 1; ++i) {
            if ((*fSetArray)[i] == (*fSetArray)[i + 1]) {
                return true;
            }
        }
        return false;
    }

    bool isSorted() const {
        for (int i = 0; i < fSetArray->count() - 1; ++i) {
            // Use only < operator
            if (!((*fSetArray)[i] < (*fSetArray)[i + 1])) {
                return false;
            }
        }
        return true;
    }

    /** Checks if fSetArray is consistent with fOrderedArray
     */
    bool arraysConsistent() const {
        if (fSetArray->count() != fOrderedArray->count()) {
            return false;
        }
        if (fOrderedArray->count() == 0) {
            return true;
        }

        // Copy and sort fOrderedArray, then compare to fSetArray.
        // A O(n log n) algorithm is necessary as O(n^2) will choke some GMs.
        SkAutoMalloc sortedArray(fOrderedArray->bytes());
        T* sortedBase = reinterpret_cast<T*>(sortedArray.get());
        int count = fOrderedArray->count();
        fOrderedArray->copyRange(sortedBase, 0, count);

        SkTQSort<T>(sortedBase, sortedBase + count - 1);

        for (int i = 0; i < count; ++i) {
            if (sortedBase[i] != (*fSetArray)[i]) {
                return false;
            }
        }

        return true;
    }
#endif

private:
    SkTDArray<T>* fSetArray;        // Sorted by pointer address for fast
                                    // lookup.
    SkTDArray<T>* fOrderedArray;    // Sorted by insertion order for
                                    // deterministic output.

    /** Returns the index in fSetArray where an element was found.
     * Returns -1 if the element was not found, and it fills *posToInsertSorted
     * with the index of the place where elem should be inserted to preserve the
     * internal array sorted.
     * If element was found, *posToInsertSorted is undefined.
     */
    int find(const T& elem, int* posToInsertSorted = NULL) const {
        SkASSERT(fSetArray);

        if (fSetArray->count() == 0) {
            if (posToInsertSorted) {
                *posToInsertSorted = 0;
            }
            return -1;
        }
        int iMin = 0;
        int iMax = fSetArray->count();

        while (iMin < iMax - 1) {
            int iMid = (iMin + iMax) / 2;
            if (elem < (*fSetArray)[iMid]) {
                iMax = iMid;
            } else {
                iMin = iMid;
            }
        }
        if (elem == (*fSetArray)[iMin]) {
            return iMin;
        }
        if (posToInsertSorted) {
            if (elem < (*fSetArray)[iMin]) {
                *posToInsertSorted = iMin;
            } else {
                *posToInsertSorted = iMin + 1;
            }
        }

        return -1;
    }
};

#endif