/*
 * Carla Tests
 * Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
 */

#include "RtLinkedList.hpp"

#include "CarlaString.hpp"
#include "CarlaMutex.hpp"

const unsigned short MIN_RT_EVENTS = 5;
const unsigned short MAX_RT_EVENTS = 12;

struct MyData {
    char str[234];
    int id;
};

struct PostRtEvents {
    CarlaMutex mutex;
    RtLinkedList<MyData>::Pool dataPool;
    RtLinkedList<MyData> data;
    RtLinkedList<MyData> dataPendingRT;

    PostRtEvents() noexcept
        : dataPool(MIN_RT_EVENTS, MAX_RT_EVENTS),
          data(dataPool),
          dataPendingRT(dataPool) {}

    ~PostRtEvents() noexcept
    {
        clear();
    }

    void appendRT(const MyData& event) noexcept
    {
        dataPendingRT.append(event);
    }

    void clear() noexcept
    {
        mutex.lock();
        data.clear();
        dataPendingRT.clear();
        mutex.unlock();
    }

    void trySplice() noexcept
    {
        if (mutex.tryLock())
        {
            dataPendingRT.moveTo(data, true);
            mutex.unlock();
        }
    }

} postRtEvents;

void run5Tests();
void run5Tests()
{
    ushort k = 0;
    MyData allMyData[MAX_RT_EVENTS];

    // Make a safe copy of events while clearing them
    postRtEvents.mutex.lock();

    while (! postRtEvents.data.isEmpty())
    {
        static MyData kFallback = { { '\0' }, 0 };
        const MyData& my(postRtEvents.data.getFirst(kFallback, true));
        allMyData[k++] = my;
    }

    postRtEvents.mutex.unlock();

    carla_stdout("Post-Rt Event Count: %i", k);
    assert(k == 5);

    // data should be empty now
    assert(postRtEvents.data.count() == 0);
    assert(postRtEvents.data.isEmpty());
    assert(postRtEvents.dataPendingRT.count() == 0);
    assert(postRtEvents.dataPendingRT.isEmpty());

    // Handle events now
    for (ushort i=0; i < k; ++i)
    {
        const MyData& my(allMyData[i]);

        carla_stdout("Got data: %i %s", my.id, my.str);
    }

    // append events past minimum size
    MyData dummyData = { { '\0' }, 0 };
    // 5 initial go ok
    assert(postRtEvents.dataPendingRT.append(dummyData));
    assert(postRtEvents.dataPendingRT.append(dummyData));
    assert(postRtEvents.dataPendingRT.append(dummyData));
    assert(postRtEvents.dataPendingRT.append(dummyData));
    assert(postRtEvents.dataPendingRT.append(dummyData));
    // afterwards it fails
    assert(! postRtEvents.dataPendingRT.append(dummyData));
    assert(! postRtEvents.dataPendingRT.append(dummyData));
    carla_stdout("here %i", __LINE__);

    // adding sleepy works
    assert(postRtEvents.dataPendingRT.append_sleepy(dummyData));
    carla_stdout("here %i", __LINE__);

    // now atomic works too, size was increased
    assert(postRtEvents.dataPendingRT.append(dummyData));
    assert(postRtEvents.dataPendingRT.append(dummyData));
    assert(postRtEvents.dataPendingRT.append(dummyData));
    assert(postRtEvents.dataPendingRT.append(dummyData));
    carla_stdout("here %i", __LINE__);

    // fails here now
    assert(! postRtEvents.dataPendingRT.append(dummyData));
    carla_stdout("here %i", __LINE__);

    // adding sleepy still works
    assert(postRtEvents.dataPendingRT.append_sleepy(dummyData));
    carla_stdout("here %i", __LINE__);

    // now atomic works for 1 more
    assert(postRtEvents.dataPendingRT.append(dummyData));
    assert(! postRtEvents.dataPendingRT.append(dummyData));
    carla_stdout("here %i", __LINE__);

    // and adding sleepy no longer works
    assert(! postRtEvents.dataPendingRT.append_sleepy(dummyData));
    carla_stdout("here %i", __LINE__);

    // cleanup
    postRtEvents.trySplice();
    postRtEvents.clear();
}

int main()
{
    MyData m1; m1.id = 1; std::strcpy(m1.str, "1");
    MyData m2; m2.id = 2; std::strcpy(m2.str, "2");
    MyData m3; m3.id = 3; std::strcpy(m3.str, "3");
    MyData m4; m4.id = 4; std::strcpy(m4.str, "4");
    MyData m5; m5.id = 5; std::strcpy(m5.str, "5");

    // start
    assert(postRtEvents.data.count() == 0);
    assert(postRtEvents.data.isEmpty());
    assert(postRtEvents.dataPendingRT.count() == 0);
    assert(postRtEvents.dataPendingRT.isEmpty());

    // single append
    postRtEvents.appendRT(m1);
    assert(postRtEvents.data.count() == 0);
    assert(postRtEvents.dataPendingRT.count() == 1);
    postRtEvents.trySplice();
    assert(postRtEvents.data.count() == 1);
    assert(postRtEvents.dataPendingRT.count() == 0);

    // +3 appends
    postRtEvents.appendRT(m2);
    postRtEvents.appendRT(m4);
    postRtEvents.appendRT(m3);
    assert(postRtEvents.data.count() == 1);
    assert(postRtEvents.dataPendingRT.count() == 3);
    postRtEvents.trySplice();
    assert(postRtEvents.data.count() == 4);
    assert(postRtEvents.dataPendingRT.count() == 0);

    for (RtLinkedList<MyData>::Itenerator it = postRtEvents.data.begin2(); it.valid(); it.next())
    {
        static const MyData kFallback = { { '\0' }, 0 };
        const MyData& my(it.getValue(kFallback));

        carla_stdout("FOR DATA!!!: %i %s", my.id, my.str);

        if (my.id == 2)
        {
            // +1 append
            postRtEvents.dataPendingRT.insertAt(m5, it);
            assert(postRtEvents.data.count() == 4);
            assert(postRtEvents.dataPendingRT.count() == 1);
        }
    }

#if 0
    for (const MyData& my : postRtEvents.data)
        carla_stdout("FOR DATA!!! NEW AUTO Itenerator!!!: %i %s", my.id, my.str);
#endif

    postRtEvents.trySplice();
    assert(postRtEvents.data.count() == 5);
    assert(postRtEvents.dataPendingRT.count() == 0);

    run5Tests();

    // reset
    postRtEvents.clear();
    assert(postRtEvents.data.count() == 0);
    assert(postRtEvents.data.isEmpty());
    assert(postRtEvents.dataPendingRT.count() == 0);
    assert(postRtEvents.dataPendingRT.isEmpty());

    // test non-rt
    const unsigned int CARLA_EVENT_DATA_ATOM    = 0x01;
    const unsigned int CARLA_EVENT_DATA_MIDI_LL = 0x04;

    LinkedList<uint32_t> evIns, evOuts;
    evIns.append(CARLA_EVENT_DATA_ATOM);
    evOuts.append(CARLA_EVENT_DATA_ATOM);
    evOuts.append(CARLA_EVENT_DATA_MIDI_LL);

    if (evIns.count() > 0)
    {
        for (size_t j=0, count=evIns.count(); j < count; ++j)
        {
            const uint32_t& type(evIns.getAt(j, 0));

            if (type == CARLA_EVENT_DATA_ATOM)
                pass();
            else if (type == CARLA_EVENT_DATA_MIDI_LL)
                pass();
        }
    }

    evIns.clear();
    evOuts.clear();

    return 0;
}
