/*
  Q Light Controller
  mastertimer_test.cpp

  Copyright (C) Heikki Junnila

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0.txt

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#include <QtTest>

#define private public
#include "mastertimer_test.h"
#include "dmxsource_stub.h"
#include "function_stub.h"
#include "mastertimer.h"
#include "qlcchannel.h"
#include "universe.h"
#include "qlcfile.h"
#include "doc.h"
#undef private

#include "../common/resource_paths.h"

void MasterTimer_Test::initTestCase()
{
    m_doc = new Doc(this);

    QDir dir(INTERNAL_FIXTUREDIR);
    dir.setFilter(QDir::Files);
    dir.setNameFilters(QStringList() << QString("*%1").arg(KExtFixture));
    QVERIFY(m_doc->fixtureDefCache()->loadMap(dir) == true);
}

void MasterTimer_Test::cleanupTestCase()
{
    delete m_doc;
}

void MasterTimer_Test::init()
{
}

void MasterTimer_Test::cleanup()
{
    m_doc->clearContents();
}

void MasterTimer_Test::initial()
{
    MasterTimer* mt = m_doc->masterTimer();

    QVERIFY(mt->runningFunctions() == 0);
    QVERIFY(mt->m_functionList.size() == 0);
    QVERIFY(mt->m_functionListMutex.tryLock() == true);
    mt->m_functionListMutex.unlock();

    QVERIFY(mt->m_dmxSourceList.size() == 0);
    QVERIFY(mt->m_dmxSourceListMutex.tryLock() == true);
    mt->m_dmxSourceListMutex.unlock();

    //QVERIFY(mt->m_running == false);
    QVERIFY(mt->m_stopAllFunctions == false);
}

void MasterTimer_Test::startStop()
{
    MasterTimer* mt = m_doc->masterTimer();

    mt->start();
    QTest::qWait(100);

    QVERIFY(mt->runningFunctions() == 0);
    QVERIFY(mt->m_functionList.size() == 0);
    QVERIFY(mt->m_dmxSourceList.size() == 0);
    // QVERIFY(mt->m_running == true);
    QVERIFY(mt->m_stopAllFunctions == false);

    mt->stop();
    QTest::qWait(100);

    QVERIFY(mt->runningFunctions() == 0);
    QVERIFY(mt->m_functionList.size() == 0);
    QVERIFY(mt->m_dmxSourceList.size() == 0);
    // QVERIFY(mt->m_running == false);
    QVERIFY(mt->m_stopAllFunctions == false);
}

void MasterTimer_Test::startStopFunction()
{
    MasterTimer* mt = m_doc->masterTimer();
    mt->start();

    Function_Stub fs(m_doc);

    QVERIFY(mt->runningFunctions() == 0);

    mt->startFunction(NULL);
    QVERIFY(mt->runningFunctions() == 0);

    mt->startFunction(&fs);
    mt->timerTick();
    QVERIFY(mt->runningFunctions() == 1);

    mt->startFunction(&fs);
    QVERIFY(mt->runningFunctions() == 1);

    QTest::qWait(100);
    fs.stop(FunctionParent::master());
    QTest::qWait(100);

    QVERIFY(mt->runningFunctions() == 0);
}

void MasterTimer_Test::registerUnregisterDMXSource()
{
    MasterTimer* mt = m_doc->masterTimer();
    QVERIFY(mt->m_dmxSourceList.size() == 0);

    DMXSource_Stub s1;
    /* Normal registration */
    mt->registerDMXSource(&s1);
    QVERIFY(mt->m_dmxSourceList.size() == 1);
    QVERIFY(mt->m_dmxSourceList.at(0) == &s1);

    /* No double additions */
    mt->registerDMXSource(&s1);
    QVERIFY(mt->m_dmxSourceList.size() == 1);
    QVERIFY(mt->m_dmxSourceList.at(0) == &s1);

    DMXSource_Stub s2;
    /* Normal registration of another source */
    mt->registerDMXSource(&s2);
    QVERIFY(mt->m_dmxSourceList.size() == 2);
    QVERIFY(mt->m_dmxSourceList.at(0) == &s1);
    QVERIFY(mt->m_dmxSourceList.at(1) == &s2);

    /* No double additions */
    mt->registerDMXSource(&s2);
    QVERIFY(mt->m_dmxSourceList.size() == 2);
    QVERIFY(mt->m_dmxSourceList.at(0) == &s1);
    QVERIFY(mt->m_dmxSourceList.at(1) == &s2);

    /* No double additions */
    mt->registerDMXSource(&s1);
    QVERIFY(mt->m_dmxSourceList.size() == 2);
    QVERIFY(mt->m_dmxSourceList.at(0) == &s1);
    QVERIFY(mt->m_dmxSourceList.at(1) == &s2);

    /* Removal of a source */
    mt->unregisterDMXSource(&s1);
    QVERIFY(mt->m_dmxSourceList.size() == 1);
    QVERIFY(mt->m_dmxSourceList.at(0) == &s2);

    /* No double removals */
    mt->unregisterDMXSource(&s1);
    QVERIFY(mt->m_dmxSourceList.size() == 1);
    QVERIFY(mt->m_dmxSourceList.at(0) == &s2);

    /* Removal of the last source */
    mt->unregisterDMXSource(&s2);
    QVERIFY(mt->m_dmxSourceList.size() == 0);
}

void MasterTimer_Test::interval()
{
    MasterTimer* mt = m_doc->masterTimer();
    Function_Stub fs(m_doc);
    DMXSource_Stub dss;

    mt->start();
    QTest::qWait(100);

    fs.start(mt, FunctionParent::master());
    mt->timerTick();
    QVERIFY(mt->runningFunctions() == 1);

    mt->registerDMXSource(&dss);
    QVERIFY(mt->m_dmxSourceList.size() == 1);

    /* Wait for one second */
    QTest::qWait(1000);

#ifndef SKIP_TEST
    /* It's not guaranteed that context switch happens exactly after 50
       cycles, so we just have to estimate here... */
    QVERIFY(fs.m_writeCalls >= 49 && fs.m_writeCalls <= 51);
    QVERIFY(dss.m_writeCalls >= 49 && dss.m_writeCalls <= 51);
#endif

    fs.stop(FunctionParent::master());
    QTest::qWait(1000);
    QVERIFY(mt->runningFunctions() == 0);

    mt->unregisterDMXSource(&dss);
    QVERIFY(mt->m_dmxSourceList.size() == 0);
}

void MasterTimer_Test::functionInitiatedStop()
{
    MasterTimer* mt = m_doc->masterTimer();
    Function_Stub fs(m_doc);

    mt->start();

    fs.start(mt, FunctionParent::master());
    mt->timerTick();
    QVERIFY(mt->runningFunctions() == 1);

    /* Wait a while so that the function starts running */
    QTest::qWait(100);

    /* Stop the function after it has been running for a while */
    fs.stop(FunctionParent::master());

    /* Wait a while so that the function stops */
    QTest::qWait(100);

    /* Verify that the function is really stopped and the correct
       pre&post handlers have been called. */
    QVERIFY(mt->runningFunctions() == 0);
    QVERIFY(fs.m_preRunCalls == 1);
    QVERIFY(fs.m_writeCalls > 0);
    QVERIFY(fs.m_postRunCalls == 1);
}

void MasterTimer_Test::runMultipleFunctions()
{
    MasterTimer* mt = m_doc->masterTimer();
    mt->start();

    Function_Stub fs1(m_doc);
    fs1.start(mt, FunctionParent::master());
    mt->timerTick();
    QVERIFY(mt->runningFunctions() == 1);

    Function_Stub fs2(m_doc);
    fs2.start(mt, FunctionParent::master());
    mt->timerTick();
    QVERIFY(mt->runningFunctions() == 2);

    Function_Stub fs3(m_doc);
    fs3.start(mt, FunctionParent::master());
    mt->timerTick();
    QVERIFY(mt->runningFunctions() == 3);

    /* Wait a while so that the functions start running */
    QTest::qWait(100);

    /* Stop the functions after they have been running for a while */
    fs1.stop(FunctionParent::master());
    fs2.stop(FunctionParent::master());
    fs3.stop(FunctionParent::master());

    /* Wait a while so that the functions stop */
    QTest::qWait(100);

    QVERIFY(mt->runningFunctions() == 0);
}

void MasterTimer_Test::stopAllFunctions()
{
    MasterTimer* mt = m_doc->masterTimer();
    mt->start();

    Function_Stub fs1(m_doc);
    fs1.start(mt, FunctionParent::master());

    DMXSource_Stub s1;
    mt->registerDMXSource(&s1);

    Function_Stub fs2(m_doc);
    fs2.start(mt, FunctionParent::master());

    DMXSource_Stub s2;
    mt->registerDMXSource(&s2);

    Function_Stub fs3(m_doc);
    fs3.start(mt, FunctionParent::master());

    QTest::qWait(60);

    QVERIFY(mt->runningFunctions() == 3);
    QVERIFY(mt->m_dmxSourceList.size() == 2);

    mt->stopAllFunctions();
    QVERIFY(mt->runningFunctions() == 0);
    QVERIFY(mt->m_dmxSourceList.size() == 2); // Shouldn't stop

    mt->unregisterDMXSource(&s1);
    mt->unregisterDMXSource(&s2);
}

void MasterTimer_Test::stop()
{
    MasterTimer* mt = m_doc->masterTimer();
    mt->start();

    Function_Stub fs1(m_doc);
    fs1.start(mt, FunctionParent::master());

    Function_Stub fs2(m_doc);
    fs2.start(mt, FunctionParent::master());

    Function_Stub fs3(m_doc);
    fs3.start(mt, FunctionParent::master());

    QTest::qWait(60);
    QVERIFY(mt->runningFunctions() == 3);

    mt->stop();
    QTest::qWait(60);
    QVERIFY(mt->runningFunctions() == 0);
    // QVERIFY(mt->m_running == false);
}

void MasterTimer_Test::restart()
{
    MasterTimer* mt = m_doc->masterTimer();
    mt->start();

    Function_Stub fs1(m_doc);
    fs1.start(mt, FunctionParent::master());

    Function_Stub fs2(m_doc);
    fs2.start(mt, FunctionParent::master());

    Function_Stub fs3(m_doc);
    fs3.start(mt, FunctionParent::master());

    QTest::qWait(60);
    QVERIFY(mt->runningFunctions() == 3);

    mt->stop();
    QTest::qWait(60);
    QVERIFY(mt->runningFunctions() == 0);
    QVERIFY(mt->m_functionList.size() == 0);
    QVERIFY(mt->m_functionListMutex.tryLock() == true);
    mt->m_functionListMutex.unlock();
    // QVERIFY(mt->m_running == false);
    QVERIFY(mt->m_stopAllFunctions == false);

    mt->start();
    QVERIFY(mt->runningFunctions() == 0);
    QVERIFY(mt->m_functionList.size() == 0);
    QVERIFY(mt->m_functionListMutex.tryLock() == true);
    mt->m_functionListMutex.unlock();
    // QVERIFY(mt->m_running == true);
    QVERIFY(mt->m_stopAllFunctions == false);

    fs1.start(mt, FunctionParent::master());
    fs2.start(mt, FunctionParent::master());
    fs3.start(mt, FunctionParent::master());
    QTest::qWait(60);
    QVERIFY(mt->runningFunctions() == 3);

    mt->stopAllFunctions();
}

QTEST_MAIN(MasterTimer_Test)
