/*
 *  Copyright (c) 2016, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. 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.
 *  3. Neither the name of the copyright holder 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 HOLDER 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.
 */

#include "test_platform.h"
#include <common/debug.hpp>
#include <common/timer.hpp>
#include <openthread-instance.h>

enum
{
    kCallCountIndexAlarmStop = 0,
    kCallCountIndexAlarmStart,
    kCallCountIndexTimerHandler,

    kCallCountIndexMax
};

uint32_t sNow;
uint32_t sPlatT0;
uint32_t sPlatDt;
bool     sTimerOn;
uint32_t sCallCount[kCallCountIndexMax];

void testTimerAlarmStop(otInstance *)
{
    sTimerOn = false;
    sCallCount[kCallCountIndexAlarmStop]++;
}

void testTimerAlarmStartAt(otInstance *, uint32_t aT0, uint32_t aDt)
{
    sTimerOn = true;
    sCallCount[kCallCountIndexAlarmStart]++;
    sPlatT0 = aT0;
    sPlatDt = aDt;
}

uint32_t testTimerAlarmGetNow(void)
{
    return sNow;
}

void InitTestTimer(void)
{
    g_testPlatAlarmStop = testTimerAlarmStop;
    g_testPlatAlarmStartAt = testTimerAlarmStartAt;
    g_testPlatAlarmGetNow = testTimerAlarmGetNow;
}

void InitCounters(void)
{
    memset(sCallCount, 0, sizeof(sCallCount));
}

void TestTimerHandler(void *aContext)
{
    sCallCount[kCallCountIndexTimerHandler]++;

    if (aContext)
    {
        (*static_cast<uint32_t *>(aContext))++;
    }
}

/**
 * Test the TimerScheduler's behavior of one timer started and fired.
 */
int TestOneTimer(void)
{
    const uint32_t kTimeT0 = 1000;
    const uint32_t kTimerInterval = 10;
    otInstance aInstance;
    Thread::Timer timer(aInstance.mIp6.mTimerScheduler, TestTimerHandler, NULL);

    // Test one Timer basic operation.

    InitTestTimer();
    InitCounters();

    sNow = kTimeT0;
    timer.Start(kTimerInterval);

    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart]    == 1, "TestOneTimer: Start CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop]     == 0, "TestOneTimer: Stop CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler]  == 0, "TestOneTimer: Handler CallCount Failed.\n");
    VerifyOrQuit(sPlatT0 == 1000 && sPlatDt == 10,              "TestOneTimer: Start params Failed.\n");
    VerifyOrQuit(timer.IsRunning(),                             "TestOneTimer: Timer running Failed.\n");
    VerifyOrQuit(sTimerOn,                                      "TestOneTimer: Platform Timer State Failed.\n");

    sNow += kTimerInterval;

    otPlatAlarmFired(&aInstance);

    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart]    == 1, "TestOneTimer: Start CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop]     == 1, "TestOneTimer: Stop CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler]  == 1, "TestOneTimer: Handler CallCount Failed.\n");
    VerifyOrQuit(timer.IsRunning() == false,                    "TestOneTimer: Timer running Failed.\n");
    VerifyOrQuit(sTimerOn == false,                             "TestOneTimer: Platform Timer State Failed.\n");

    // Test one Timer that spans the 32-bit wrap.

    InitCounters();

    sNow = 0 - (kTimerInterval - 2);
    timer.Start(kTimerInterval);

    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart]    == 1,         "TestOneTimer: Start CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop]     == 0,         "TestOneTimer: Stop CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler]  == 0,         "TestOneTimer: Handler CallCount Failed.\n");
    VerifyOrQuit(sPlatT0 == 0 - (kTimerInterval - 2) && sPlatDt == 10,  "TestOneTimer: Start params Failed.\n");
    VerifyOrQuit(timer.IsRunning(),                                     "TestOneTimer: Timer running Failed.\n");
    VerifyOrQuit(sTimerOn,                                              "TestOneTimer: Platform Timer State Failed.\n");

    sNow += kTimerInterval;

    otPlatAlarmFired(&aInstance);

    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart]    == 1, "TestOneTimer: Start CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop]     == 1, "TestOneTimer: Stop CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler]  == 1, "TestOneTimer: Handler CallCount Failed.\n");
    VerifyOrQuit(timer.IsRunning() == false,                    "TestOneTimer: Timer running Failed.\n");
    VerifyOrQuit(sTimerOn == false,                             "TestOneTimer: Platform Timer State Failed.\n");

    // Test one Timer that is late by several msec

    InitCounters();

    sNow = kTimeT0;
    timer.Start(kTimerInterval);

    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart]    == 1, "TestOneTimer: Start CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop]     == 0, "TestOneTimer: Stop CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler]  == 0, "TestOneTimer: Handler CallCount Failed.\n");
    VerifyOrQuit(sPlatT0 == 1000 && sPlatDt == 10,              "TestOneTimer: Start params Failed.\n");
    VerifyOrQuit(timer.IsRunning(),                             "TestOneTimer: Timer running Failed.\n");
    VerifyOrQuit(sTimerOn,                                      "TestOneTimer: Platform Timer State Failed.\n");

    sNow += kTimerInterval + 5;

    otPlatAlarmFired(&aInstance);

    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart]    == 1, "TestOneTimer: Start CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop]     == 1, "TestOneTimer: Stop CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler]  == 1, "TestOneTimer: Handler CallCount Failed.\n");
    VerifyOrQuit(timer.IsRunning() == false,                    "TestOneTimer: Timer running Failed.\n");
    VerifyOrQuit(sTimerOn == false,                             "TestOneTimer: Platform Timer State Failed.\n");

    // Test one Timer that is early by several msec

    InitCounters();

    sNow = kTimeT0;
    timer.Start(kTimerInterval);

    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart]    == 1, "TestOneTimer: Start CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop]     == 0, "TestOneTimer: Stop CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler]  == 0, "TestOneTimer: Handler CallCount Failed.\n");
    VerifyOrQuit(sPlatT0 == 1000 && sPlatDt == 10,              "TestOneTimer: Start params Failed.\n");
    VerifyOrQuit(timer.IsRunning(),                             "TestOneTimer: Timer running Failed.\n");
    VerifyOrQuit(sTimerOn,                                      "TestOneTimer: Platform Timer State Failed.\n");

    sNow += kTimerInterval - 2;

    otPlatAlarmFired(&aInstance);

    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart]    == 2, "TestOneTimer: Start CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop]     == 0, "TestOneTimer: Stop CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler]  == 0, "TestOneTimer: Handler CallCount Failed.\n");
    VerifyOrQuit(timer.IsRunning() == true,                     "TestOneTimer: Timer running Failed.\n");
    VerifyOrQuit(sTimerOn == true,                              "TestOneTimer: Platform Timer State Failed.\n");

    sNow += kTimerInterval;

    otPlatAlarmFired(&aInstance);

    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart]    == 2, "TestOneTimer: Start CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop]     == 1, "TestOneTimer: Stop CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler]  == 1, "TestOneTimer: Handler CallCount Failed.\n");
    VerifyOrQuit(timer.IsRunning() == false,                    "TestOneTimer: Timer running Failed.\n");
    VerifyOrQuit(sTimerOn == false,                             "TestOneTimer: Platform Timer State Failed.\n");

    return 0;
}

/**
 * Test the TimerScheduler's behavior of ten timers started and fired.
 */
int TestTenTimers(void)
{
    const uint32_t kNumTimers = 10;
    const uint32_t kNumTriggers = 7;
    const uint32_t kTimeT0[kNumTimers] =
    {
        1000,
        1000,
        1001,
        1002,
        1003,
        1004,
        1005,
        1006,
        1007,
        1008
    };
    const uint32_t kTimerInterval[kNumTimers] =
    {
        20,
        100,
        (0 - kTimeT0[2]),
        100000,
        1000000,
        10,
        (1000 - kTimeT0[6]),
        200,
        200,
        200
    };
    // Expected timer fire order
    // timer #    Trigger time
    //   5         1014
    //   0         1020
    //   1         1100
    //   7         1206
    //   8         1207
    //   9         1208
    //   3       101002
    //   4      1001003
    //   2            0 <timer wrapped>
    //   6         1000 <timer wrapped>
    const uint32_t kTriggerTimes[kNumTriggers] =
    {
        1014,
        1020,
        1100,
        1207,
        101004,
        /* timer wrap here */
        2,
        1000
    };
    // Expected timers fired by each kTriggerTimes[] value
    //  Trigger #    Timers Fired
    //    0             5
    //    1             0
    //    2             1
    //    3             7, 8
    //    4             9, 3
    //    5             4, 2
    //    6             6
    const bool kTimerStateAfterTrigger [kNumTriggers][kNumTimers] =
    {
        {  true,  true,  true,  true,  true, false, true,   true,  true,  true},  // 5
        { false,  true,  true,  true,  true, false, true,   true,  true,  true},  // 0
        { false, false,  true,  true,  true, false, true,   true,  true,  true},  // 1
        { false, false,  true,  true,  true, false, true,  false, false,  true},  // 7, 8
        { false, false,  true, false,  true, false, true,  false, false, false},  // 9, 3
        { false, false, false, false, false, false, true,  false, false, false},  // 4, 2
        { false, false, false, false, false, false, false, false, false, false}   // 6
    };

    const bool kSchedulerStateAfterTrigger[kNumTriggers] =
    {
        true,
        true,
        true,
        true,
        true,
        true,
        false
    };

    const uint32_t kTimerHandlerCountAfterTrigger[kNumTriggers] =
    {
        1,
        2,
        3,
        5,
        7,
        9,
        10
    };

    const uint32_t kTimerStopCountAfterTrigger[kNumTriggers] =
    {
        0,
        0,
        0,
        0,
        0,
        0,
        1
    };

    const uint32_t kTimerStartCountAfterTrigger[kNumTriggers] =
    {
        3,
        4,
        5,
        7,
        9,
        11,
        11
    };

    otInstance aInstance;

    uint32_t timerContextHandleCounter[kNumTimers] = {0};
    Thread::Timer timer0(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[0]);
    Thread::Timer timer1(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[1]);
    Thread::Timer timer2(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[2]);
    Thread::Timer timer3(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[3]);
    Thread::Timer timer4(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[4]);
    Thread::Timer timer5(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[5]);
    Thread::Timer timer6(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[6]);
    Thread::Timer timer7(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[7]);
    Thread::Timer timer8(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[8]);
    Thread::Timer timer9(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[9]);
    Thread::Timer *timers[kNumTimers] = {&timer0, &timer1, &timer2, &timer3, &timer4, &timer5, &timer6, &timer7, &timer8, &timer9};
    size_t i;

    // Start the Ten timers.

    InitTestTimer();
    InitCounters();

    for (i = 0; i < kNumTimers ; i++)
    {
        sNow = kTimeT0[i];
        timers[i]->Start(kTimerInterval[i]);
    }

    // given the order in which timers are started, the TimerScheduler should call otPlatAlarmStartAt 2 times.
    // one for timer[0] and one for timer[5] which will supercede timer[0].
    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart]    == 2,         "TestTenTimer: Start CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop]     == 0,         "TestTenTimer: Stop CallCount Failed.\n");
    VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler]  == 0,         "TestTenTimer: Handler CallCount Failed.\n");
    VerifyOrQuit(sPlatT0 == kTimeT0[5] && sPlatDt == kTimerInterval[5], "TestTenTimer: Start params Failed.\n");
    VerifyOrQuit(sTimerOn,                                              "TestTenTimer: Platform Timer State Failed.\n");

    for (i = 0 ; i < kNumTimers ; i++)
    {
        VerifyOrQuit(timers[i]->IsRunning(), "TestTenTimer: Timer running Failed.\n");
    }

    // Issue the triggers and test the State after each trigger.

    for (size_t trigger = 0 ; trigger < kNumTriggers ; trigger++)
    {
        sNow = kTriggerTimes[trigger];

        do
        {
            // By design, each call to otPlatAlarmFired() can result in 0 or 1 calls to a timer handler.
            // For some combinations of sNow and Timers queued, it is necessary to call otPlatAlarmFired()
            // multiple times in order to handle all the expired timers.  It can be determined that another
            // timer is ready to be triggered by examining the aDt arg passed into otPlatAlarmStartAt().  If
            // that value is 0, then otPlatAlarmFired should be fired immediately. This loop calls otPlatAlarmFired()
            // the requisite number of times based on the aDt argument.
            otPlatAlarmFired(&aInstance);
        }
        while (sPlatDt == 0);

        VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart]    == kTimerStartCountAfterTrigger[trigger],
                     "TestTenTimer: Start CallCount Failed.\n");
        VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop]     == kTimerStopCountAfterTrigger[trigger],
                     "TestTenTimer: Stop CallCount Failed.\n");
        VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler]  == kTimerHandlerCountAfterTrigger[trigger],
                     "TestTenTimer: Handler CallCount Failed.\n");
        VerifyOrQuit(sTimerOn                                 == kSchedulerStateAfterTrigger[trigger],
                     "TestTenTimer: Platform Timer State Failed.\n");

        for (i = 0 ; i < kNumTimers ; i++)
        {
            VerifyOrQuit(timers[i]->IsRunning() == kTimerStateAfterTrigger[trigger][i], "TestTenTimer: Timer running Failed.\n");
        }
    }

    for (i = 0 ; i < kNumTimers ; i++)
    {
        VerifyOrQuit(timerContextHandleCounter[i] == 1, "TestTenTimer: Timer context counter Failed.\n");
    }

    return 0;
}

void RunTimerTests(void)
{
    TestOneTimer();
    TestTenTimers();
}

#ifdef ENABLE_TEST_MAIN
int main(void)
{
    RunTimerTests();
    printf("All tests passed\n");
    return 0;
}
#endif
