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

#include "include/private/base/SkTDArray.h"
#include "src/base/SkRandom.h"
#include "src/base/SkTDPQueue.h"
#include "tests/Test.h"

namespace { bool intless(const int& a, const int& b) { return a < b; } }

static void simple_test(skiatest::Reporter* reporter) {
    SkTDPQueue<int, intless> heap;
    REPORTER_ASSERT(reporter, 0 == heap.count());

    heap.insert(0);
    REPORTER_ASSERT(reporter, 1 == heap.count());
    REPORTER_ASSERT(reporter, 0 == heap.peek());
    heap.pop();
    REPORTER_ASSERT(reporter, 0 == heap.count());

    heap.insert(0);
    heap.insert(1);
    REPORTER_ASSERT(reporter, 2 == heap.count());
    REPORTER_ASSERT(reporter, 0 == heap.peek());
    heap.pop();
    REPORTER_ASSERT(reporter, 1 == heap.count());
    REPORTER_ASSERT(reporter, 1 == heap.peek());
    heap.pop();
    REPORTER_ASSERT(reporter, 0 == heap.count());

    heap.insert(2);
    heap.insert(1);
    heap.insert(0);
    REPORTER_ASSERT(reporter, 3 == heap.count());
    REPORTER_ASSERT(reporter, 0 == heap.peek());
    heap.pop();
    REPORTER_ASSERT(reporter, 2 == heap.count());
    REPORTER_ASSERT(reporter, 1 == heap.peek());
    heap.pop();
    REPORTER_ASSERT(reporter, 1 == heap.count());
    REPORTER_ASSERT(reporter, 2 == heap.peek());
    heap.pop();
    REPORTER_ASSERT(reporter, 0 == heap.count());

    heap.insert(2);
    heap.insert(3);
    heap.insert(0);
    heap.insert(1);
    REPORTER_ASSERT(reporter, 4 == heap.count());
    REPORTER_ASSERT(reporter, 0 == heap.peek());
    heap.pop();
    REPORTER_ASSERT(reporter, 3 == heap.count());
    REPORTER_ASSERT(reporter, 1 == heap.peek());
    heap.pop();
    REPORTER_ASSERT(reporter, 2 == heap.count());
    REPORTER_ASSERT(reporter, 2 == heap.peek());
    heap.pop();
    REPORTER_ASSERT(reporter, 1 == heap.count());
    REPORTER_ASSERT(reporter, 3 == heap.peek());
    heap.pop();
    REPORTER_ASSERT(reporter, 0 == heap.count());
}

struct Mock {
    int fValue;
    int fPriority;
    mutable int fIndex;

    static bool LessP(Mock* const& a, Mock* const& b) { return a->fPriority < b->fPriority; }
    static int* PQIndex(Mock* const& mock) { return &mock->fIndex; }

    bool operator== (const Mock& that) const {
        return fValue == that.fValue && fPriority == that.fPriority;
    }
    bool operator!= (const Mock& that) const { return !(*this == that); }
};

void random_test(skiatest::Reporter* reporter) {
    SkRandom random;
    static const Mock kSentinel = {-1, -1, -1};

    for (int i = 0; i < 100; ++i) {
        // Create a random set of Mock objects.
        int count = random.nextULessThan(100);
        SkTDArray<Mock> array;
        array.reserve(count);
        for (int j = 0; j < count; ++j) {
            Mock* mock = array.append();
            mock->fPriority = random.nextS();
            mock->fValue = random.nextS();
            mock->fIndex = -1;
            if (*mock == kSentinel) {
                array.pop_back();
                --j;
            }
        }

        // Stick the mock objects in the pqueue.
        SkTDPQueue<Mock*, Mock::LessP, Mock::PQIndex> pq;
        for (int j = 0; j < count; ++j) {
            pq.insert(&array[j]);
        }
        REPORTER_ASSERT(reporter, pq.count() == array.size());
        for (int j = 0; j < count; ++j) {
            // every item should have an entry in the queue.
            REPORTER_ASSERT(reporter, -1 != array[j].fIndex);
        }

        // Begin the test.
        while (pq.count()) {
            // Make sure the top of the queue is really the highest priority.
            Mock* top = pq.peek();
            for (int k = 0; k < count; ++k) {
                REPORTER_ASSERT(reporter, kSentinel == array[k] ||
                                            array[k].fPriority >= top->fPriority);
            }
            // Do one of three random actions:
            unsigned action = random.nextULessThan(3);
            switch (action) {
                case 0: { // pop the top,
                    top = pq.peek();
                    REPORTER_ASSERT(reporter, array.begin() <= top && top < array.end());
                    pq.pop();
                    *top = kSentinel;
                    break;
                }
                case 1: { // remove a random element,
                    int item;
                    do {
                        item = random.nextULessThan(count);
                    } while (array[item] == kSentinel);
                    pq.remove(&array[item]);
                    array[item] = kSentinel;
                    break;
                }
                case 2: { // or change an element's priority.
                    int item;
                    do {
                        item = random.nextULessThan(count);
                    } while (array[item] == kSentinel);
                    array[item].fPriority = random.nextS();
                    pq.priorityDidChange(&array[item]);
                    break;
                }
            }
        }
   }
}

void sort_test(skiatest::Reporter* reporter) {
    SkRandom random;

    SkTDPQueue<Mock *, Mock::LessP, Mock::PQIndex> pqTest;
    SkTDPQueue<Mock *, Mock::LessP, Mock::PQIndex> pqControl;

    // Create a random set of Mock objects and populate the test queue.
    int count = random.nextULessThan(100);
    SkTDArray<Mock> testArray;
    testArray.reserve(count);
    for (int i = 0; i < count; i++) {
        Mock *mock = testArray.append();
        mock->fPriority = random.nextS();
        mock->fValue = random.nextS();
        mock->fIndex = -1;
        pqTest.insert(&testArray[i]);
    }

    // Stick equivalent mock objects into the control queue.
    SkTDArray<Mock> controlArray;
    controlArray.reserve(count);
    for (int i = 0; i < count; i++) {
        Mock *mock = controlArray.append();
        mock->fPriority = testArray[i].fPriority;
        mock->fValue = testArray[i].fValue;
        mock->fIndex = -1;
        pqControl.insert(&controlArray[i]);
    }

    // Sort the queue
    pqTest.sort();

    // Compare elements in the queue to ensure they are in sorted order
    int prevPriority = pqTest.peek()->fPriority;
    for (int i = 0; i < count; i++) {
        REPORTER_ASSERT(reporter, i <= pqTest.at(i)->fIndex);
        REPORTER_ASSERT(reporter, prevPriority <= pqTest.at(i)->fPriority);
        prevPriority = pqTest.at(i)->fPriority;
    }

    // Verify that after sorting the queue still produces the same result as the control queue
    for (int i = 0; i < count; i++) {
        REPORTER_ASSERT(reporter, *pqControl.peek() == *pqTest.peek());
        pqControl.pop();
        pqTest.pop();
    }
}

DEF_TEST(TDPQueueTest, reporter) {
    simple_test(reporter);
    random_test(reporter);
    sort_test(reporter);
}
