// main.cpp
// Copyright (C) MicroNeil Research Corporation
//
// This is the certification test for the timing module.
// Most of these tests are about basic sanity.
// If something doesn't look sane then the program returns the line number
// starting the tests that failed. Otherwise the program returns zero.
//
// 20060404 _M Added testing for Timer::start(msclock startt);
//
// 20060403 _M Polished this off and successfully tested on RHES3 (g++)
// and on WINXP (mingw) with comparable results. This is ready for production.
// The thing I wish is that on win32 platforms the timing functions were more
// robuts and reliable -- but they're not. No matter what way you slice it,
// any kind of real-time functionality on the Windows platform is going to be
// problematic and application specific. That said, this new timing module does
// provide reasonably accurate functions for most applications that don't need
// to interact in real-time.

#include <iostream>
#include <cmath>
#include "timing.hpp"

using namespace std;

char* TestName = "none";
int TestLine = 0;

#define NowTesting(x) _NowTesting(__LINE__,x);

class FoundABugException {};

void _NowTesting(int l, char* x) {
    TestName = x;
    TestLine = l;
    cout << endl << "Testing " << TestLine << ": " << x << endl;
}

static const int X30SECS = 30000;                                               // 30 second timeout value.

class ThirtySecondTimeout: public Timeout {                                     // Make a 30 second timeout extended class.
    public:
        ThirtySecondTimeout():Timeout(X30SECS) {}
};

int main() {
    try {

        NowTesting("Timing Module Certification.");

        // Keep track of the running time with timerx as part of the cert.

        NowTesting("Timer timerx;");
        Timer timerx;

        // Certify Sleeper.

        // Sleeper()
        // Sleeper(int x)
        // setMillisecondsToSleep()
        // getMillisecondsToSleep()
        // sleep();
        // sleep(int x);

        NowTesting("Sleeper Certifications...");

        NowTesting("BadSleeperValue exceptions");

        static const int BadMinSleeperTime = MinimumSleeperTime - 1;
        static const int BadMaxSleeperTime = MaximumSleeperTime + 1;
        static const int NominalSleeperTime = 10;

        bool ExceptionsHappen = false;                                          // Create a reset test flag.
        try {
            Sleeper T(BadMinSleeperTime);                                       // Test for a bad low sleeper time.
        } catch (Sleeper::BadSleeperValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "BadMinSleeperTime exception works." << endl;
        } else {
            cout << "BadMinSleeperTime execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            Sleeper T(BadMaxSleeperTime);                                       // Test for a bad high sleeper time.
        } catch (Sleeper::BadSleeperValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "BadMaxSleeperTime exception works." << endl;
        } else {
            cout << "BadMaxSleeperTime execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            Sleeper T;                                                          // Test for an unset sleeper time.
            T.sleep();                                                          // Try to sleep with that!
        } catch (Sleeper::BadSleeperValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Unset Sleep Time exception works." << endl;
        } else {
            cout << "Unset Sleep Time execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            Sleeper T;
            T.setMillisecondsToSleep(BadMinSleeperTime);                        // Test setting bad low after construction.
        } catch (Sleeper::BadSleeperValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Bad low after construction exception works." << endl;
        } else {
            cout << "Bad low after construction execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            Sleeper T;
            T.setMillisecondsToSleep(BadMaxSleeperTime);                        // Test setting bad high after construction.
        } catch (Sleeper::BadSleeperValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Bad high after construction exception works." << endl;
        } else {
            cout << "Bad high after construction execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            Sleeper T;
            T.sleep(BadMinSleeperTime);                                         // Test setting bad low sleep(x)
        } catch (Sleeper::BadSleeperValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Bad low on sleep(x) exception works." << endl;
        } else {
            cout << "Bad low on sleep(x) execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            Sleeper T;
            T.sleep(BadMaxSleeperTime);                                         // Test setting bad high sleep(x)
        } catch (Sleeper::BadSleeperValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Bad high on sleep(x) exception works." << endl;
        } else {
            cout << "Bad high on sleep(x) execption failed!" << endl;
            throw FoundABugException();
        }

        NowTesting("Sleeper Sleeper1;");
        Sleeper Sleeper1;

        NowTesting("Sleeper Sleeper200(200);");
        Sleeper Sleeper200(200);

        NowTesting("Sleeper200.getMillisecondsToSleep();");
        if(200==Sleeper200.getMillisecondsToSleep()) {
            cout << "Sleeper200.getMilliscondsToSleep() OK!" << endl;
        }

        // SWIGGLE is 15ms because the actual sleep time can be effected by
        // operating system and operating conditions. Also, on WIN32 systems the
        // measured time in a timer can be off by as much as 15ms because the
        // time values are only set (all be it accurately) 64 times per second
        // or so on XP. A higher resolution timer could be created, but it would
        // be overkill for most of the applications this code is intended to
        // support.

        static const int SWIGGLE = 15;                                          // Wiggle room for sleep times.
        static const int FIRST_SLEEPER1_TIME = 92;                              // Time for first spleeper experiment.

        NowTesting("Sleeper1.setMillisecondsToSleep(FIRST_LEEPER1_TIME)");
        Sleeper1.setMillisecondsToSleep(FIRST_SLEEPER1_TIME);
        NowTesting("Sleeper1.getMillisecondsToSleep()");
        if(FIRST_SLEEPER1_TIME != Sleeper1.getMillisecondsToSleep()) {
            cout << "getMillisecondsToSleep() did not match setMillisecondsToSleep()!" << endl;
            throw FoundABugException();
        }

        NowTesting("First Elapsed Time from timerx & Build T1 to test Sleeper1.");
        cout << "Time elapsed: " << timerx.getElapsedSeconds() << endl;
        Timer T1;
        NowTesting("Sleeper1.sleep();");
        Sleeper1.sleep();
        T1.stop();
        cout << "Time elapsed: " << timerx.getElapsedSeconds() << endl;
        cout << "T1 time slept: " << T1.getElapsedTime() << endl;
        int delta;
        if(SWIGGLE > (delta = abs((long long int) T1.getElapsedTime() - Sleeper1.getMillisecondsToSleep()))) {
            cout << "T1.getElapsedTime - Sleeper1.getMillisecondsToSleep() within "
                 << SWIGGLE << "ms - OK! (" << delta << "ms)" << endl;
        } else {
            cout << "T1.getElapsedTime - Sleeper1.getMillisecondsToSleep() is not in range! (" << delta << "ms)" << endl;
            throw FoundABugException();
        }

        int SECOND_SLEEPER1_TIME = 532;                                         // Time for second sleeper experiment.

        NowTesting("T1.restart & Sleeper.sleep(x)");
        NowTesting("Sleeper1.sleep(SECOND_SLEEPER1_TIME);");
        T1.start();
        Sleeper1.sleep(SECOND_SLEEPER1_TIME);
        T1.stop();
        cout << "Time elapsed: " << timerx.getElapsedSeconds() << endl;
        cout << "T1 time slept: " << T1.getElapsedTime() << endl;
        cout << "Sleeper1.getMillisecondsToSleep(): " << Sleeper1.getMillisecondsToSleep() << endl;
        delta = abs((long long int) T1.getElapsedTime() - Sleeper1.getMillisecondsToSleep());
        if(SWIGGLE > delta) {
            cout << "T1.getElapsedTime - Sleeper1.getMillisecondsToSleep() within 15 ms - OK! (" << delta << "ms)" << endl;
        } else {
            cout << "T1.getElapsedTime - Sleeper1.getMillisecondsToSleep() is not in range! (" << delta << "ms)" << endl;
            throw FoundABugException();
        }


        // Certify PollTimer

        // PollTimer(int Nom, int Max)
        // setNominalPollTime(int Nom)
        // setMaximumPollTime(int Max)
        // reset()
        // pause()

        NowTesting("PollTimer exceptions...");

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            PollTimer T(BadMinSleeperTime,NominalSleeperTime);                  // Test bad low nom exception.
        } catch (PollTimer::BadPollTimerValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Bad low nominal construction exception works." << endl;
        } else {
            cout << "Bad low nominal construction execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            PollTimer T(BadMaxSleeperTime,NominalSleeperTime);                  // Test bad high nom exception.
        } catch (PollTimer::BadPollTimerValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Bad high nominal construction exception works." << endl;
        } else {
            cout << "Bad high nominal construction execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            PollTimer T(NominalSleeperTime,BadMinSleeperTime);                  // Test bad low max exception.
        } catch (PollTimer::BadPollTimerValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Bad low max construction exception works." << endl;
        } else {
            cout << "Bad low max construction execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            PollTimer T(NominalSleeperTime,BadMaxSleeperTime);                  // Test bad high max exception.
        } catch (PollTimer::BadPollTimerValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Bad high max construction exception works." << endl;
        } else {
            cout << "Bad high max construction execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            PollTimer T(NominalSleeperTime,NominalSleeperTime);
            T.setNominalPollTime(BadMinSleeperTime);                            // Test bad nom change exception.
        } catch (PollTimer::BadPollTimerValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Bad low nom change exception works." << endl;
        } else {
            cout << "Bad low nom change execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            PollTimer T(NominalSleeperTime,NominalSleeperTime);
            T.setNominalPollTime(BadMaxSleeperTime);                            // Test high nom change exception.
        } catch (PollTimer::BadPollTimerValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Bad high nom change exception works." << endl;
        } else {
            cout << "Bad high nom change execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            PollTimer T(NominalSleeperTime,NominalSleeperTime);
            T.setMaximumPollTime(BadMinSleeperTime);                            // Test low max change exception.
        } catch (PollTimer::BadPollTimerValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Bad low max change exception works." << endl;
        } else {
            cout << "Bad low max change execption failed!" << endl;
            throw FoundABugException();
        }

        ExceptionsHappen = false;                                               // Reset the test flag.
        try {
            PollTimer T(NominalSleeperTime,NominalSleeperTime);
            T.setMaximumPollTime(BadMaxSleeperTime);                            // Test high max change exception.
        } catch (PollTimer::BadPollTimerValue) {
            ExceptionsHappen = true;
        }

        if(true == ExceptionsHappen) {
            cout << "Bad high max change exception works." << endl;
        } else {
            cout << "Bad high max change execption failed!" << endl;
            throw FoundABugException();
        }

        NowTesting("PollTimer PT1(PTNOMINAL, PTMAXIMUM);");

        static const int PTNOMINAL = 10;
        static const int PTMAXIMUM = 1000;
        static const int MAX_POLLTIMER_LOOP_COUNT = 100;

        PollTimer PT1(PTNOMINAL, PTMAXIMUM);

        NowTesting("First pause time = Min");
        int LastPollTime = 0;
        T1.start();
        LastPollTime = PT1.pause();
        T1.stop();
        if(PTNOMINAL == LastPollTime) {
            cout << "PT1 Min Poll Time indicated on first pass OK!" << endl;
        } else {
            cout << "PT1 Min Poll Time NOT INDICATED ON FIRST PASS!" << endl;
            throw FoundABugException();
        }

        delta = abs((long long int) T1.getElapsedTime() - PTNOMINAL);
        cout << "Elapsed on first poll was: " << T1.getElapsedTime() << endl;
        if(SWIGGLE > delta) {
            cout << "PT1 Min Poll Time .wiggle on first poll was OK! ("
                 << delta << ")" << endl;
        } else {
            cout << "PT1 Min Poll Time wiggle on firlst poll was BAD! ("
                 << delta << ")" << endl;
            throw FoundABugException();
        }

        NowTesting("PT1.pause() in a loop to Max poll time.");
        int LoopCount = 0;
        while(                                                                  // Loop while...
          LastPollTime < PTMAXIMUM &&                                           // We've not reached our maximum pause time and
          MAX_POLLTIMER_LOOP_COUNT > LoopCount) {                               // We've not exceeded our loop count.
            LoopCount++;                                                        // Count the loops.
            T1.start();                                                         // Start the clock to measure the event.
            LastPollTime = PT1.pause();                                         // Pause per loop.
            T1.stop();                                                          // Stop the clock measuring the event.
            cout << "Loop[" << LoopCount                                        // Report per loop.
                 << "] Last Poll Time: " << LastPollTime << endl;
            cout << "    Measured: " << T1.getElapsedTime() << endl;            // Report the measured time.
            delta = abs((long long int) T1.getElapsedTime() - LastPollTime);
            if(SWIGGLE < delta) {                                               // Check for too much wiggle.
                cout << "SWIGGLE EXCEEDED!" << endl;                            // Report and throw if we find it.
                throw FoundABugException();
            }
        }

        NowTesting("Max pause time reached and correct?");
        if(PTMAXIMUM == LastPollTime) {
            cout << "Indicated Poll Time is correct. SWIGGLE passed!" << endl;
        } else {
            cout << "Indicated Poll Time NOT MAX!" << endl;
            throw FoundABugException();
        }

        NowTesting("PT1.reset();");
        PT1.reset();

        // Certify Timer

        // Timer()
        // Timer(msclock startt)
        // start()
        // start(msclock startt)
        // stop()
        // getStartClock()
        // getElapsedTime()
        // getStopClock()
        // isRunning()
        // getElapsedSeconds()
        // isUnixBased()
        // toWindowsEpoch(msclock unixt)
        // toUnixEpoch(msclock win32t)

        NowTesting("TX, TY, TZ chaining & basic functions.");

        Timer TX;                                                               // Create TX Timer.
        Sleeper200.sleep();                                                     // Sleep a bit.
        Timer TY(TX.stop());                                                    // Create TY Timer chained from TX.
        Sleeper200.sleep();                                                     // Sleep a bit.

        if(true == TY.isRunning()) {                                            // Test the isRunning() method when running.
            cout << "TY.isRunning() seems correct." << endl;
        } else {
            cout << "TY.isRunning() IS NOT CORRECT!" << endl;
            throw FoundABugException();
        }

        if(false == TX.isRunning()) {                                           // Test the isRunning() method when not running.
            cout << "TX.isRunning() seems correct." << endl;
        } else {
            cout << "TX.isRunning() IS NOT CORRECT!" << endl;
            throw FoundABugException();
        }

        Sleeper200.sleep();                                                     // Sleep again for fun.
        Timer TZ(TY.stop());                                                    // Create TZ timer chained from TY.
        Sleeper200.sleep();                                                     // Sleep 1
        Sleeper200.sleep();                                                     // 2...
        Sleeper200.sleep();                                                     // 3 times the charm.
        timerx.start(TZ.stop());                                                // Stop TZ and restart timerx.

        if(timerx.getStartClock() == TZ.getStopClock()) {
            cout << "timerx.start(TZ.stop()) worked as expected." << endl;
        } else {
            cout << "timerx.start(TZ.stop()) is broken!" << endl;
            throw FoundABugException();
        }


        NowTesting("getStartClock, getStopClock, and getElapsedTime");

        cout << "TX start clock: " << TX.getStartClock() << endl;
        cout << "TX stop clock: " << TX.getStopClock() << endl;
        long long int MeasureElapsed = TX.getStopClock() - TX.getStartClock();
        cout << "TX external calc of elapsed ms = " << MeasureElapsed << endl;
        cout << "TX getElapsedTime() returns: " << TX.getElapsedTime() << endl;
        if(TX.getElapsedTime() == MeasureElapsed) {
            cout << "TX getElapsedTime() and MeasureElapsed match ok!" << endl;
        } else {
            cout << "TX.getElapsedTime() and MeasureElapsed DO NOT MATCH!" << endl;
            throw FoundABugException();
        }

        NowTesting("getElapsedSeconds() math");
        double ElapsedShouldBe = (3 * 200) / 1000;                              // We waited 3 & 200 milliseconds.
        cout << "TZ.getElapsedSeconds() returns: "
             << TZ.getElapsedSeconds() << endl;
        static const double SecondsWiggle = SWIGGLE / 1000;                     // Calculate wiggle as seconds.
        double SecondsDelta = abs(ElapsedShouldBe-TZ.getElapsedSeconds());      // Calculate the delta.
        cout << "SecondsDelta is: " << SecondsDelta << endl;
        if(SecondsWiggle < SecondsDelta) {
            cout << "Seconds Look Good from getElapsedSeconds()." << endl;
        } else {
            cout << "Seconds DONT MATCH from getElapsedSeconds()!" << endl;
            throw FoundABugException();
        }

        NowTesting("Does the TX, TY, TZ chain match?");
        if(                                                                     // Start and end clocks on the chain should match.
          TX.getStopClock() == TY.getStartClock() &&
          TY.getStopClock() == TZ.getStartClock()
          ) {
            cout << "Chained timers work ok." << endl;
        } else {
            cout << "Cained timers broken!" << endl;
            throw FoundABugException();
        }

        NowTesting("isUnixBased() Correct?");

        #ifdef WIN32
            if(false == TZ.isUnixBased()) {                                     // On a WIN32 system this should be false.
                cout << "isUnixBased() appears correct." << endl;
            } else {
                cout << "isUnixBased() appears broken!" << endl;
                throw FoundABugException();
            }

        #else
            if(true == TZ.isUnixBased()) {                                      // On a non WIN32 system this should be true.
                cout << "isUnixBased() appears correct." << endl;
            } else {
                cout << "isUnixBased() appears broken!" << endl;
                throw FoundABugException();
            }
        #endif

        NowTesting("toWindowsEpoch() and toUnixEpoch()");

        if(                                                                     // Epoch conversion should be symmetcal.
          TZ.toWindowsEpoch(TZ.getStopClock()) != TZ.getStopClock() &&
          TZ.getStopClock() == TZ.toUnixEpoch(TZ.toWindowsEpoch(TZ.getStopClock()))
          ) {
            cout << "Epoch Conversions seem correct." << endl;
        } else {
            cout << "Epoch Conversions are not symmetrical!" << endl;
            throw FoundABugException();
        }

        // Certify Timeout

        // Timeout(msclock duration)
        // setDuration(msclock duration)
        // restart()
        // getElapsedTime()
        // getRemainingTime()
        // isExpired()

        NowTesting("ThirtySecondTimeout X30");

        ThirtySecondTimeout X30;                                                // Create a 30 second timout by extension.

        if(X30SECS == X30.getDuration()) {                                     // Test getDuration();
            cout << "getDuration() seems sane." << endl;
        } else {
            cout << "getDuration() is broken!" << endl;
            throw FoundABugException();
        }

        NowTesting("Timeout.getElapsedTime & .getRemainingTime()");

        msclock PreElapsed = X30.getElapsedTime();
        msclock PreRemaining = X30.getRemainingTime();
        T1.start();
        Sleeper200.sleep();
        msclock PostElapsed = X30.getElapsedTime();
        msclock PostRemaining = X30.getRemainingTime();
        T1.stop();
        long long int ElapsedCalc = PostElapsed - PreElapsed;
        long long int RemainingCalc = PreRemaining - PostRemaining;

        delta = abs((long long int) T1.getElapsedTime() - ElapsedCalc);
        if(SWIGGLE > delta) {
            cout << "getElapsedTime() seems sane." << endl;
        } else {
            cout << "getElapsedTime() is broken!" << endl;
            throw FoundABugException();
        }

        delta = abs((long long int) T1.getElapsedTime() - RemainingCalc);
        if(SWIGGLE > delta) {
            cout << "getRemainingTime() seems sane." << endl;
        } else {
            cout << "getRemainingTime() is broken!" << endl;
            throw FoundABugException();
        }

        NowTesting("X30 count down...");

        static const int ONESEC = 1000;

        PollTimer XX(ONESEC,ONESEC);                                            // Count down every second...
        while(false == X30.isExpired()) {
            cout << "X30.getElapsedTime() = " << X30.getElapsedTime() << endl;
            cout << "X30.getRemainingTime() = " << X30.getRemainingTime() << endl;
            XX.pause();
        }

        NowTesting("X30 Remaining value after expiration.");
        Sleeper200.sleep();                                                     // After we've expired wait a bit...

        if(0 == X30.getRemainingTime()) {
            cout << "X30 zero remaining after timeout ok." << endl;
        } else {
            cout << "X30 NOT ZERO AFTER TIMEOUT!" << endl;
            throw FoundABugException();
        }

        NowTesting("X30 Restart function");
        X30.restart();                                                          // After a restart we should have 30 secs.
        msclock X30NewRemaining = X30.getRemainingTime();
        delta = abs((long long int) X30NewRemaining - X30SECS);
        if(SWIGGLE > delta && false == X30.isExpired()) {
            cout << "X30 Restart to 30 secs OK." << endl;
        } else {
            cout << "X30 Restart BROKEN!" << endl;
            cout << "X30 reports time left is: " << X30NewRemaining << endl;
            throw FoundABugException();
        }

    }
    catch(...) {
        cout << "Unexpected Error In Test " << TestName << endl;
        return (TestLine);
    }

    cout << endl << "All OK!" << endl;
    return (0);
}