/*
 * Copyright (c) 2019 Inria
 * All rights reserved
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Daniel Carvalho
 */

#include <gtest/gtest.h>

#include <utility>

#include "base/sat_counter.hh"

/**
 * Test if the maximum value is indeed the maximum value reachable.
 */
TEST(SatCounterTest, MaximumValue)
{
    const unsigned bits = 3;
    const unsigned max_value = (1 << bits) - 1;
    SatCounter counter(bits);

    for (int i = 0; i < 2*max_value; i++) {
        counter++;
    }

    ASSERT_EQ(counter, max_value);
}

/**
 * Test if the minimum value is indeed the mimimum value reachable.
 */
TEST(SatCounterTest, MinimumValue)
{
    const unsigned bits = 3;
    SatCounter counter(bits);

    for (int i = 0; i < 2; i++) {
        counter--;
    }

    ASSERT_EQ(counter, 0);
}

/**
 * Test initializing the counter with a value, updating it and then resetting.
 */
TEST(SatCounterTest, InitialValue)
{
    const unsigned bits = 3;
    const unsigned initial_value = 4;
    SatCounter counter(bits, initial_value);
    ASSERT_EQ(counter, initial_value);
    counter++;
    counter.reset();
    ASSERT_EQ(counter, initial_value);
}

/**
 * Test calculating saturation percentile.
 */
TEST(SatCounterTest, SaturationPercentile)
{
    const unsigned bits = 3;
    const unsigned max_value = (1 << bits) - 1;
    SatCounter counter(bits);

    ASSERT_FALSE(counter.isSaturated());
    for (double value = 0.0; value <= max_value; value++, counter++) {
        const double saturation = value / max_value;
        ASSERT_DOUBLE_EQ(counter.calcSaturation(), saturation);
    }
    ASSERT_TRUE(counter.isSaturated());
}

/**
 * Test abrupt saturation.
 */
TEST(SatCounterTest, Saturate)
{
    const unsigned bits = 3;
    const unsigned max_value = (1 << bits) - 1;
    SatCounter counter(bits);
    counter++;
    ASSERT_FALSE(counter.isSaturated());

    // Make sure the value added is what was missing to saturate
    const unsigned diff = counter.saturate();
    ASSERT_EQ(diff, max_value - 1);
    ASSERT_TRUE(counter.isSaturated());
}

/**
 * Test back and forth against an int.
 */
TEST(SatCounterTest, IntComparison)
{
    const unsigned bits = 3;
    SatCounter counter(bits);
    int value = 0;

    ASSERT_EQ(counter++, value++);
    ASSERT_EQ(counter++, value++);
    ASSERT_EQ(counter--, value--);
    ASSERT_EQ(counter++, value++);
    ASSERT_EQ(counter++, value++);
    ASSERT_EQ(counter--, value--);
    ASSERT_EQ(counter++, value++);
    ASSERT_EQ(counter--, value--);
    ASSERT_EQ(counter--, value--);
    ASSERT_EQ(counter++, value++);
    ASSERT_EQ(counter--, value--);
    ASSERT_EQ(counter--, value--);
    ASSERT_EQ(counter, 0);
}

/**
 * Test shift operators.
 */
TEST(SatCounterTest, Shift)
{
    const unsigned bits = 3;
    const unsigned max_value = (1 << bits) - 1;
    const unsigned initial_value = 1;
    SatCounter counter(bits, initial_value);
    SatCounter other(bits, initial_value);
    // The saturated shift value is just enough to saturate, since greater
    // values could generate undefined behavior
    SatCounter saturated_counter(bits, bits);
    int value = initial_value;

    // Test random shifts
    counter <<= 2;
    value <<= 2;
    ASSERT_EQ(counter, value);
    counter >>= 1;
    value >>= 1;
    ASSERT_EQ(counter, value);

    // Test saturation
    counter <<= bits;
    ASSERT_EQ(counter, max_value);

    // Test zeroing
    counter >>= bits;
    ASSERT_EQ(counter, 0);

    // Test saturation against other saturating counter
    counter.reset();
    value = initial_value;
    counter <<= other;
    value <<= other;
    ASSERT_EQ(counter, value);
    counter <<= saturated_counter;
    value = max_value;
    ASSERT_EQ(counter, max_value);

    // Test zeroing against other saturating counter
    counter >>= other;
    value >>= other;
    ASSERT_EQ(counter, value);
    counter >>= saturated_counter;
    ASSERT_EQ(counter, 0);
}

/**
 * Test both pre and post operators.
 */
TEST(SatCounterTest, PrePostOperators)
{
    const unsigned bits = 3;
    const unsigned max_value = (1 << bits) - 1;
    SatCounter counter_pre(bits);
    SatCounter counter_post(bits);

    for (int i = 0; i < 2*max_value; i++) {
        counter_post++;
        SatCounter value_pre = ++counter_pre;
        ASSERT_EQ(counter_post, value_pre);
    }

    ASSERT_EQ(counter_pre, max_value);
    ASSERT_EQ(counter_post, max_value);

    for (int i = 0; i < 2*max_value; i++) {
        counter_post--;
        SatCounter value_pre = --counter_pre;
        ASSERT_EQ(counter_post, value_pre);
    }

    ASSERT_EQ(counter_pre, 0);
    ASSERT_EQ(counter_post, 0);
}

/**
 * Test copy and move for both constructor and assignment.
 */
TEST(SatCounterTest, CopyMove)
{
    const unsigned bits = 3;
    const unsigned max_value = (1 << bits) - 1;
    const unsigned initial_value = 1;
    SatCounter counter(bits, initial_value);
    SatCounter deep_copy(1);
    SatCounter counter_copy(2);

    // Increase counter value so that we can check if the inner counter is
    // being copied
    counter++;

    // Copy counter using both the copy constructor and the copy assignment
    SatCounter counter_copy_constructor(counter);
    deep_copy = counter_copy = counter;
    ASSERT_EQ(counter_copy_constructor, initial_value + 1);
    ASSERT_EQ(counter_copy, initial_value + 1);
    ASSERT_EQ(deep_copy, initial_value + 1);

    // Make sure max value is the same for all of them, and that modifying
    // the copies does not modify the original
    for (int i = 0; i < 2*max_value; i++) {
        counter_copy_constructor++;
        counter_copy++;
        deep_copy++;
    }
    ASSERT_EQ(counter, initial_value + 1);
    ASSERT_EQ(counter_copy_constructor, max_value);
    ASSERT_EQ(counter_copy, max_value);
    ASSERT_EQ(deep_copy, max_value);

    // Make sure initial value is the same for all of them
    counter_copy_constructor.reset();
    counter_copy.reset();
    deep_copy.reset();
    ASSERT_EQ(counter_copy_constructor, initial_value);
    ASSERT_EQ(counter_copy, initial_value);
    ASSERT_EQ(deep_copy, initial_value);

    // Now check move
    SatCounter counter_move_constructor(std::move(counter));
    ASSERT_EQ(counter, 0);
    ASSERT_EQ(counter_move_constructor, initial_value + 1);

    SatCounter counter_move(bits);
    counter_move = std::move(counter_move_constructor);
    ASSERT_EQ(counter_move_constructor, 0);
    ASSERT_EQ(counter_move, initial_value + 1);
}

/**
 * Test add-assignment and subtract assignment.
 */
TEST(SatCounterTest, AddSubAssignment)
{
    const unsigned bits = 3;
    const unsigned max_value = (1 << bits) - 1;
    SatCounter counter(bits);
    SatCounter other(bits, 2);
    SatCounter saturated_counter(bits, max_value);
    int value = 0;

    // Test add-assignment for a few random values and then saturate
    counter += 2;
    value += 2;
    ASSERT_EQ(counter, value);
    counter += 3;
    value += 3;
    ASSERT_EQ(counter, value);
    counter += max_value;
    value = max_value;
    ASSERT_EQ(counter, value);

    // Test subtract-assignment for a few random values until back to zero
    counter -= 2;
    value -= 2;
    ASSERT_EQ(counter, value);
    counter -= 3;
    value -= 3;
    ASSERT_EQ(counter, value);
    counter -= max_value;
    value = 0;
    ASSERT_EQ(counter, value);

    // Test add-assignment of other saturating counter
    counter += other;
    value += other;
    ASSERT_EQ(counter, value);
    counter += saturated_counter;
    value = max_value;
    ASSERT_EQ(counter, saturated_counter);

    // Test subtract-assignment of other saturating counter
    counter -= other;
    value -= other;
    ASSERT_EQ(counter, value);
    counter -= saturated_counter;
    ASSERT_EQ(counter, 0);
}