//- Copyright (c) 2008-2020 James Grenning --- All rights reserved
//- For exclusive use by participants in Wingman Software training courses.
//- Cannot be used by attendees to train others without written permission.
//- www.wingman-sw.com james@wingman-sw.com

#include "CppUTest/TestHarness.h"

extern "C"
{
#include "CircularBuffer.h"
}

#define BUF_CAPACITY 10

TEST_GROUP(CircularBuffer)
{
    CircularBuffer * buffer;

    void setup()
    {
        buffer = CircularBuffer_Create(BUF_CAPACITY);
    }

    void teardown()
    {
        CircularBuffer_Destroy(buffer);
    }
    
    void fillTheQueue(int seedValue, int numberOfElements)
    {
        for(size_t i = 0; i < BUF_CAPACITY; i++)
        {
            int val = seedValue + i;
            CircularBuffer_Put(buffer, val);
        }
    }
    
    void emptyTheQueue()
    {
        for (size_t i = 0; i < BUF_CAPACITY; i++)
        {
            CircularBuffer_Get(buffer);
        }
    }
};

TEST(CircularBuffer, create_destroy)
{
//    FAIL("Start here");
}

TEST(CircularBuffer, is_empty_after_creation)
{
    CHECK_TRUE(CircularBuffer_IsEmpty(buffer));
}

TEST(CircularBuffer, is_not_full_after_creation)
{
    CHECK_FALSE(CircularBuffer_IsFull(buffer));
}

TEST(CircularBuffer, is_not_empty_after_put)
{
    CircularBuffer_Put(buffer, 10046);
    CHECK_FALSE(CircularBuffer_IsEmpty(buffer));
}

TEST(CircularBuffer, is_empty_after_put_then_get)
{
    CircularBuffer_Put(buffer, 4567);
    CircularBuffer_Get(buffer);
    CHECK_TRUE(CircularBuffer_IsEmpty(buffer));
}

TEST(CircularBuffer, put_get_one_value)
{
    CircularBuffer_Put(buffer, 4567);
    LONGS_EQUAL(4567, CircularBuffer_Get(buffer));
}

TEST(CircularBuffer, put_get_is_fifo)
{
    CircularBuffer_Put(buffer, 1);
    CircularBuffer_Put(buffer, 2);
    CircularBuffer_Put(buffer, 3);
    LONGS_EQUAL(1, CircularBuffer_Get(buffer));
    LONGS_EQUAL(2, CircularBuffer_Get(buffer));
    LONGS_EQUAL(3, CircularBuffer_Get(buffer));
}

TEST(CircularBuffer, report_capacity)
{
    LONGS_EQUAL(10, CircularBuffer_Capacity(buffer));
}

TEST(CircularBuffer, create_sets_capacity)
{
    CircularBuffer * buffer = CircularBuffer_Create(2);
    LONGS_EQUAL(2, CircularBuffer_Capacity(buffer));
    CircularBuffer_Destroy(buffer);
}

TEST(CircularBuffer, is_full_when_filled_to_capacity)
{
    CircularBuffer * buffer = CircularBuffer_Create(5);
    
    CircularBuffer_Put(buffer,1);
    CircularBuffer_Put(buffer,2);
    CircularBuffer_Put(buffer,3);
    CircularBuffer_Put(buffer,4);
    CircularBuffer_Put(buffer,5);
    
    CHECK_TRUE( CircularBuffer_IsFull(buffer) );
    CircularBuffer_Destroy(buffer);
}

TEST(CircularBuffer, is_not_full_after_get_from_full_buffer)
{
    CircularBuffer * buffer = CircularBuffer_Create(5);
    
    CircularBuffer_Put(buffer,1);
    CircularBuffer_Put(buffer,2);
    CircularBuffer_Put(buffer,3);
    CircularBuffer_Put(buffer,4);
    CircularBuffer_Put(buffer,5);
    
    CircularBuffer_Get(buffer);
    
    CHECK_FALSE( CircularBuffer_IsFull(buffer) );
    CircularBuffer_Destroy(buffer);
}

TEST(CircularBuffer, fill_to_capacity_then_empty)
{
    fillTheQueue(27,BUF_CAPACITY);
    emptyTheQueue();
    
    CHECK_TRUE(CircularBuffer_IsEmpty(buffer));
}



/* PLEASE DELETE PREVIOUS COMMENTS AS YOU GET TESTS TO PASS */

/*
 * Discussion with James about next test:
 * Finally, we have to do the wrap around test.
 *
 * Let's fill the buffer, then take something out
 * and then add a obviously different value.  Then
 * we'll make sure all values are retrieved in FIFO.
 */

TEST(CircularBuffer, force_a_buffer_wraparound)
{
    CircularBuffer * buffer = CircularBuffer_Create(3);
    
    CircularBuffer_Put(buffer,1);
    CircularBuffer_Put(buffer,2);
    CircularBuffer_Put(buffer,3);
    CircularBuffer_Get(buffer);
    CircularBuffer_Put(buffer,555);
    
    CHECK(CircularBuffer_IsFull(buffer));
    
    CircularBuffer_Destroy(buffer);
}

TEST(CircularBuffer, is_not_empty_when_filled_to_capacity)
{
    CircularBuffer * buffer  = CircularBuffer_Create(3);
    
    CircularBuffer_Put(buffer,1);
    CircularBuffer_Put(buffer,2);
    CircularBuffer_Put(buffer,3);
    
    CHECK_FALSE(CircularBuffer_IsEmpty(buffer));
    CircularBuffer_Destroy(buffer);
}

TEST(CircularBuffer, put_to_full_fails)
{
    CircularBuffer * buffer  = CircularBuffer_Create(3);
    
    CircularBuffer_Put(buffer,1);
    CircularBuffer_Put(buffer,2);
    CircularBuffer_Put(buffer,3);
    
    CHECK_FALSE(CircularBuffer_Put(buffer,4));
    CircularBuffer_Destroy(buffer);
}

/* PLEASE DELETE PREVIOUS COMMENTS AS YOU GET TESTS TO PASS */


/*
 * Discussion with James about next test:
 * What should happen when putting to a full queue?
 * It should not damage the queue contents or state.
 */

TEST(CircularBuffer, put_to_full_does_not_damage_contents)
{
    fillTheQueue(0,BUF_CAPACITY);
    CircularBuffer_Put(buffer,99);
    
    for (int i = 0; i < BUF_CAPACITY; i++)
    {
        LONGS_EQUAL(i, CircularBuffer_Get(buffer));
    }
}

#ifdef Move_this_line_down_one_test_to_simulate_james_writing_the_test

/* PLEASE DELETE PREVIOUS COMMENTS AS YOU GET TESTS TO PASS */

/*
 * Retrospective:
 * I'm not totally happy with put to full. Maybe we need to
 * talk to the other users of this.  Maybe we should keep the
 * latest value and toss the earlier one.  It might be nice
 * to see an error on the console or in a log.
 */

/*
 * Retrospective:
 * Do you have duplicate index wrapping code in your production
 * code?  You should refactor it into a helper function.
 */

/*
 * Discussion with James about next test:
 * What should happen when we get from an empty queue?
 * We have to return something.  Are all values valid? We could
 * have an invalid value. Maybe we should return the last value
 * again?  Should you add an pointer to an error to populate?
 *
 * After deliberation, we decided to add a default value to
 * Create, so the user of the CircularBuffer could control
 * their own default return result.
 */

TEST(CircularBuffer, get_from_empty_returns_default_value)
{
    /*
     * How do you want to provide the default value?
     * Putting it into Create seems like a good choice.
     */
}

/* PLEASE DELETE PREVIOUS COMMENTS AS YOU GET TESTS TO PASS */

#endif

/* Congratulations!
 *
 * Look for test refactoring opportunities.
 *
 * Did you get rid of all those unneeded comments?
 *
 * Do it again next week except without my tests
 * to guide you
 */

/*
 * More if you got this far:
 *
 * New requirements came in, the buffer needs to handle
 * a single producer and a single consumer, and it cannot
 * have any OS locking mechanism in the solution.
 *
 * After some research, you discover that using a single
 * empty cell to to detect the full situation means that
 * the a single producer and a single consumer is thread
 * safe.  Refactor your design to use this technique.
 */


/**********************************************************
 *
 * Empty/Full Design Problem and Considerations
 *
 * We earlier looked at the diagram for several
 * CircularBuffer scenarios.  But we did not draw
 * the scenario for the full CircularBuffer.
 *
 * If you followed my lead and used input_index and
 * output_index to determine empty, you have a
 * problem. Empty and full scenarios look the same!
 *
 *
 * Empty
 *
 *       -------------------
 *       |  |  |  |  |  |  |
 *       -------------------
 *           ^^
 *           ||
 *           / \
 *          /   input_index
 *         output_index
 *
 *
 * Full
 *       -------------------
 *       |22|17|33|64|00|14|
 *       -------------------
 *              ^^
 *              ||
 *              / \
 *             /   \
 *            /     output_index
 *      input_index
 *
 * Not empty/full and not wrapped
 *
 *       -------------------
 *       |  |53|12|19|  |  |
 *       -------------------
 *           ^        ^
 *           |        |
 *           /         \
 *          /           input_index
 *         output_index
 *
 *
 * Not empty/full and is wrapped
 *
 *       -------------------
 *       |  |  |12|19|42|86|
 *       -------------------
 *        ^      ^
 *        |      |
 *        /       \
 *       /         output_index
 *      input_index
 *
 *
 *
 * Have you ever started down a path programming and
 * then discovered a critical flaw?
 *
 * I'd like you now to consider WTSTTCPW!
 *
 * (What's The Simplest Thing That Could Possibly Work.)
 *
 * I've seen many complex solution attempts that won't work.
 * Keep it simple. If you are aware of the 'extra-cell'
 * design, do not use it now. There are simpler designs.
 * We'll consider the extra-cell design in the next exercise.
 *
 * If you are not sure how to continue, there are a
 * couple hints in the next comment block.
 *
 *********************************************************/


/*********************************************************
 *
 * Hints for Empty/Full Detection.
 *
 * There are a couple simple approaches. Each hint refers
 * to a specific approach.
 *
 * 1) How many items are in an empty CircualrBuffer?
 *
 * 2) How can you differentiate the two scenarios where
 *    input_index == output_index?
 *
 * 3) If you are familar with the extra cell design, don't
 *    use it now.  The next exercise we'll convert the
 *    CircularBuffer to that design.
 *
 *********************************************************/

