
/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 *
 * 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 "qpid/sys/Timer.h"
#include "qpid/sys/Monitor.h"
#include "qpid/Options.h"
#include "unit_test.h"
#include <math.h>
#include <iostream>
#include <memory>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>

using namespace qpid::sys;
using boost::intrusive_ptr;
using boost::dynamic_pointer_cast;

namespace qpid {
namespace tests {

class Counter
{
    Mutex lock;
    uint counter;
  public:
    Counter() : counter(0) {}
    uint next()
    {
        Mutex::ScopedLock l(lock);
        return ++counter;
    }
};

class TestTask : public TimerTask
{
    const AbsTime start;
    const Duration expected;
    AbsTime end;
    bool fired;
    uint position;
    Monitor monitor;
    Counter& counter;

  public:
    TestTask(Duration timeout, Counter& _counter)
        : TimerTask(timeout, "Test"), start(now()), expected(timeout), end(start), fired(false), counter(_counter) {}

    void fire()
    {
        Monitor::ScopedLock l(monitor);
        fired = true;
        position = counter.next();
        end = now();
        monitor.notify();
    }

    void check(uint expected_position, uint64_t tolerance = 500 * TIME_MSEC)
    {
        Monitor::ScopedLock l(monitor);
        BOOST_CHECK(fired);
        BOOST_CHECK_EQUAL(expected_position, position);
        Duration actual(start, end);
#ifdef _MSC_VER
        uint64_t difference = _abs64(expected - actual);
#elif defined(_WIN32)
        uint64_t difference = labs(expected - actual);
#elif defined(__SUNPRO_CC)
        uint64_t difference = llabs(expected - actual);
#else
        uint64_t difference = abs(expected - actual);
#endif
        std::string msg(boost::lexical_cast<std::string>(boost::format("tolerance = %1%, difference = %2%") % tolerance % difference));
        BOOST_CHECK_MESSAGE(difference < tolerance, msg);
    }

    void wait(Duration d)
    {
        Monitor::ScopedLock l(monitor);
        monitor.wait(AbsTime(now(), d));
    }
};

class DummyRunner : public Runnable
{
  public:
    void run() {}
};

QPID_AUTO_TEST_SUITE(TimerTestSuite)

QPID_AUTO_TEST_CASE(testGeneral)
{
    Counter counter;
    Timer timer;
    intrusive_ptr<TestTask> task1(new TestTask(Duration(3 * TIME_SEC), counter));
    intrusive_ptr<TestTask> task2(new TestTask(Duration(1 * TIME_SEC), counter));
    intrusive_ptr<TestTask> task3(new TestTask(Duration(4 * TIME_SEC), counter));
    intrusive_ptr<TestTask> task4(new TestTask(Duration(2 * TIME_SEC), counter));

    timer.add(task1);
    timer.add(task2);
    timer.add(task3);
    timer.add(task4);

    dynamic_pointer_cast<TestTask>(task3)->wait(Duration(6 * TIME_SEC));

    dynamic_pointer_cast<TestTask>(task1)->check(3);
    dynamic_pointer_cast<TestTask>(task2)->check(1);
    dynamic_pointer_cast<TestTask>(task3)->check(4);
    dynamic_pointer_cast<TestTask>(task4)->check(2);
}

std::string toString(Duration d) { return boost::lexical_cast<std::string>(d); }
Duration fromString(const std::string& str) { return boost::lexical_cast<Duration>(str); }

QPID_AUTO_TEST_CASE(testOstreamInOut) {
    std::string empty;
    BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_SEC))), "1s");
    BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_SEC*123.4))), "123.4s");
    BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_MSEC*123.4))), "123.4ms");
    BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_USEC*123.4))), "123.4us");
    BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_NSEC*123))), "123ns");

    BOOST_CHECK_EQUAL(fromString("123.4"), Duration(int64_t(TIME_SEC*123.4)));
    BOOST_CHECK_EQUAL(fromString("123.4s"), Duration(int64_t(TIME_SEC*123.4)));
    BOOST_CHECK_EQUAL(fromString("123ms"), Duration(int64_t(TIME_MSEC*123)));
    BOOST_CHECK_EQUAL(fromString("123us"), Duration(int64_t(TIME_USEC*123)));
    BOOST_CHECK_EQUAL(fromString("123ns"), Duration(int64_t(TIME_NSEC*123)));

    Duration d = 0;
    std::istringstream i;
    std::string s;

    i.str("123x");
    i >> d;
    BOOST_CHECK(i.fail());
    BOOST_CHECK_EQUAL(d, 0);
    BOOST_CHECK_EQUAL(i.str(), "123x");

    i.str("xxx");
    i >> d;
    BOOST_CHECK(i.fail());
    BOOST_CHECK_EQUAL(d, 0);
    BOOST_CHECK_EQUAL(i.str(), "xxx");
}

QPID_AUTO_TEST_CASE(testOptionParse) {
    Options opts;
    Duration interval;
    opts.addOptions()("interval", optValue(interval, "I"), "blah");
    const char *args[] = { "fakeexe", "--interval", "123.4" };
    opts.parse(sizeof(args)/sizeof(args[0]), args);
    BOOST_CHECK_EQUAL(interval, Duration(int64_t(TIME_SEC*123.4)));
}

QPID_AUTO_TEST_SUITE_END()

}} // namespace qpid::tests
