// Copyright (C) 2013 Rob Caelers <robc@krandor.nl>
// All rights reserved.
//
// 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 3 of the License, or
// (at your option) 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.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <gtest/gtest.h>

#include <boost/signals2.hpp>
#include <boost/lexical_cast.hpp>

#include "utils/ITimeSource.hh"
#include "utils/TimeSource.hh"

#include "Timer.hh"
#include "TimePred.hh"
#include "SimulatedTime.hh"

using namespace std;
using namespace workrave::utils;
using namespace workrave;

class TestTimePred : public TimePred
{
public:
  void set(int64_t t)
  {
    next = t;
  }

  int64_t get_next(int64_t last_time)
  {
    if (next <= last_time)
      {
        next += 100;
      }
    return next;
  }

  std::string to_string() const
  {
    return "xx";
  }

  int64_t next{0};
};

class Fixture
{
public:
  Fixture()
  {
    sim = SimulatedTime::create();
  }

  ~Fixture() = default;

  void init()
  {
    sim->reset();
    TimeSource::sync();
    timer = make_timer();
  }

  Timer::Ptr make_timer()
  {
    timer = std::make_shared<Timer>("test");

    timer->set_limit(100);
    timer->set_limit_enabled(true);
    timer->set_auto_reset(20);
    timer->set_auto_reset_enabled(true);
    timer->set_snooze(50);
    timer->enable();
    return timer;
  }

  void tick(bool active) const
  {
    TimerInfo info{};
    info.event = TIMER_EVENT_NONE;
    info.idle_time = 0;
    info.elapsed_time = 0;

    TimeSource::sync();
    timer->process(active ? ACTIVITY_ACTIVE : ACTIVITY_IDLE, info);
    sim->current_time += 1000000;
  }

  void tick(bool active, int seconds, const std::function<void(int count)> &check_func) const
  {
    for (int i = 0; i < seconds; i++)
      {
        try
          {
            TimerInfo info{};
            info.event = TIMER_EVENT_NONE;
            info.idle_time = 0;
            info.elapsed_time = 0;

            TimeSource::sync();
            timer->process(active ? ACTIVITY_ACTIVE : ACTIVITY_IDLE, info);
            {
              SCOPED_TRACE("Timer");
              SCOPED_TRACE(::testing::Message() << "Count:" << i);
              SCOPED_TRACE(::testing::Message() << "Active: " << timer->get_elapsed_time());
              SCOPED_TRACE(::testing::Message() << "Idle:   " << timer->get_elapsed_idle_time());
              SCOPED_TRACE(::testing::Message() << "Overdue " << timer->get_total_overdue_time());
              check_func(i);
            }
            sim->current_time += 1000000;
          }
        catch (...)
          {
            std::cout << "error at:" << i << std::endl;
            throw;
          }
      }
  }

  void tick(bool active, int seconds, const std::function<void(int count, TimerEvent event)> &check_func)
  {
    for (int i = 0; i < seconds; i++)
      {
        try
          {
            TimerInfo info{};
            info.event = TIMER_EVENT_NONE;
            info.idle_time = 0;
            info.elapsed_time = 0;

            TimeSource::sync();
            timer->process(active ? ACTIVITY_ACTIVE : ACTIVITY_IDLE, info);
            TimerEvent event = info.event;
            {
              SCOPED_TRACE("Timer");
              SCOPED_TRACE(::testing::Message() << "Count:" << i);
              SCOPED_TRACE(::testing::Message() << "Event:" << event);

              SCOPED_TRACE(::testing::Message() << "Active: " << timer->get_elapsed_time());
              SCOPED_TRACE(::testing::Message() << "Idle:   " << timer->get_elapsed_idle_time());
              SCOPED_TRACE(::testing::Message() << "Overdue " << timer->get_total_overdue_time());
              check_func(i, event);
            }
            sim->current_time += 1000000;
          }
        catch (...)
          {
            std::cout << "error at:" << i << std::endl;
            throw;
          }
      }
  }

  SimulatedTime::Ptr sim;
  Timer::Ptr timer;
};

class TimerTest : public ::testing::Test, public Fixture
{};

TEST_F(TimerTest, test_timer_get_id)
{
  init();
  ASSERT_EQ(timer->get_id(), "test");
}

TEST_F(TimerTest, test_timer_initial_settings)
{
  init();
  ASSERT_EQ(timer->get_limit(), 100);
  ASSERT_EQ(timer->is_limit_enabled(), true);
  ASSERT_EQ(timer->get_auto_reset(), 20);
  ASSERT_EQ(timer->is_auto_reset_enabled(), true);
  ASSERT_EQ(timer->get_snooze(), 50);
}

TEST_F(TimerTest, test_timer_elasped_time)
{
  init();

  tick(false, 200, [this](int count) { ASSERT_EQ(timer->get_elapsed_time(), 0); });

  tick(true, 200, [this](int count) { ASSERT_EQ(timer->get_elapsed_time(), count); });

  tick(false, 20, [this](int count) { ASSERT_EQ(timer->get_elapsed_time(), 200); });

  tick(false, 200, [this](int count) { ASSERT_EQ(timer->get_elapsed_time(), 0); });
}

TEST_F(TimerTest, test_timer_elasped_time_when_timer_disabled)
{
  init();

  ASSERT_EQ(timer->is_enabled(), true);

  tick(false, 200, [this](int count) { ASSERT_EQ(timer->get_elapsed_time(), 0); });

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_elapsed_time(), count); });

  timer->disable();
  ASSERT_EQ(timer->is_enabled(), false);

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_elapsed_time(), 99); });

  tick(false, 20, [this](int count) { ASSERT_EQ(timer->get_elapsed_time(), 99); });

  tick(true, 200, [this](int count) { ASSERT_EQ(timer->get_elapsed_time(), 99); });
}

TEST_F(TimerTest, test_timer_elasped_idle_time)
{
  init();

  // Next: tick(false, 200, [=,this](int count) { ASSERT_EQ(timer->get_elapsed_idle_time(), 20 + count); });
  tick(false, 200, [this](int count) { ASSERT_EQ(timer->get_elapsed_idle_time(), count < 20 ? 20 + count : count); });

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_elapsed_idle_time(), 0); });

  tick(false, 200, [this](int count) { ASSERT_EQ(timer->get_elapsed_idle_time(), count); });

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_elapsed_idle_time(), 0); });
}

TEST_F(TimerTest, test_timer_total_overdue_time)
{
  init();

  tick(false, 200, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 0); });

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 0); });

  tick(true, 200, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), count); });

  tick(false, 21, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 200); });

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 200); });

  tick(true, 200, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 200 + count); });
}

TEST_F(TimerTest, test_timer_total_overdue_time_daily_reset_while_active_and_not_overdue)
{
  init();

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 0); });

  tick(true, 200, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), count); });

  tick(false, 21, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 200); });

  tick(true, 50, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 200); });

  timer->daily_reset_timer();

  tick(true, 50, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 0); });

  tick(true, 50, [this](int count) {
    //  the current overdue time is not reset, only the total
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });
}

TEST_F(TimerTest, test_timer_total_overdue_time_daily_reset_while_active_and_overdue)
{
  init();

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 0); });

  tick(true, 200, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), count); });

  tick(false, 21, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 200); });

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 200); });

  tick(true, 50, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 200 + count); });

  timer->daily_reset_timer();

  tick(true, 50, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 50 + count); });

  tick(true, 50, [this](int count) {
    // The current overdue time is not reset, only the total
    ASSERT_EQ(timer->get_total_overdue_time(), 100 + count);
  });
}

TEST_F(TimerTest, test_timer_total_overdue_time_daily_reset_while_idle_and_not_overdue)
{
  init();

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 0); });

  tick(true, 200, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), count); });

  tick(false, 21, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 200); });

  timer->daily_reset_timer();

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 0); });

  tick(true, 200, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), count); });
}

TEST_F(TimerTest, test_timer_total_overdue_time_daily_reset_while_idle_and_overdue)
{
  init();

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 0); });

  tick(true, 200, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), count); });

  tick(false, 21, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 200); });

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 200); });

  tick(true, 70, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 200 + count); });

  tick(false, 10, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 270); });

  timer->daily_reset_timer();

  tick(true, 100, [this](int count) { ASSERT_EQ(timer->get_total_overdue_time(), 70 + count); });
}

TEST_F(TimerTest, test_timer_stop_timer_while_active_and_not_overdue)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  timer->stop_timer();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 99 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count < 1 ? 0 : count - 1);
    ASSERT_EQ(event, (count - 1) % 50 == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_stop_timer_while_active_and_overdue)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 40, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });

  timer->stop_timer();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 139 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count < 1 ? 39 : count + 39);
    ASSERT_EQ(event, (count - 11) % 50 == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_stop_timer_while_idle_and_overdue)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count % 50 == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  tick(false, 20, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 200);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 100);
  });

  timer->stop_timer();

  tick(false, 200, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 20 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 100);
  });
}

TEST_F(TimerTest, test_timer_stop_timer_while_idle_and_not_overdue)
{
  init();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 20, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 50);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->stop_timer();

  tick(false, 200, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_NATURAL_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 20 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });
}

TEST_F(TimerTest, test_timer_reset_timer_while_active_and_not_overdue)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->reset_timer();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 99 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 1 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });
}

TEST_F(TimerTest, test_timer_reset_timer_while_active_and_overdue)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count % 50 == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  timer->reset_timer();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 99 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 1 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 99);
  });
}

TEST_F(TimerTest, test_timer_reset_timer_while_idle_and_not_overdue)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->reset_timer();

  tick(false, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 21 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });
}

TEST_F(TimerTest, test_timer_reset_timer_while_idle__overdue)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count % 50 == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 200);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 100);
  });

  timer->reset_timer();

  tick(false, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 21 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 100);
  });
}

TEST_F(TimerTest, test_timer_freeze_timer_when_active)
{
  init();

  tick(true, 100, [this](int count) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  ASSERT_EQ(timer->get_elapsed_time(), 99);
  timer->freeze_timer(true);
  ASSERT_EQ(timer->get_elapsed_time(), 99);

  tick(false, 5, [this](int count, TimerEvent event) {
    timer->freeze_timer(true);
    ASSERT_EQ(timer->get_elapsed_time(), 99);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 60, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 99);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 5);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  timer->freeze_timer(false);

  tick(true, 50, [this](int count, TimerEvent event) {
    timer->freeze_timer(false);
    // FIXME: why increased to 100?
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_freeze_timer_when_idle_defrost_when_active)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(timer->is_running(), true);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(timer->is_running(), false);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  timer->freeze_timer(true);

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 5 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(timer->is_running(), false);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 60, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 10);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(timer->is_running(), true);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  ASSERT_EQ(timer->get_elapsed_time(), 100);
  ASSERT_EQ(timer->is_running(), true);
  timer->freeze_timer(false);
  ASSERT_EQ(timer->get_elapsed_time(), 100);

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 101);
    // FIXME: idle time reset to 0? -> need to process unfreeze in process
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 1);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 40, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 101 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 1 + count);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_freeze_timer_when_idle_defrost_when_idle)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(timer->is_running(), true);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(timer->is_running(), false);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  timer->freeze_timer(true);

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 5 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(timer->is_running(), false);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 60, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 10);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(timer->is_running(), true);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  ASSERT_EQ(timer->get_elapsed_time(), 100);
  ASSERT_EQ(timer->is_running(), true);

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 10 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  ASSERT_EQ(timer->get_elapsed_time(), 100);
  ASSERT_EQ(timer->is_running(), false);

  timer->freeze_timer(false);
  ASSERT_EQ(timer->get_elapsed_time(), 100);

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 15 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 40, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_lower_limit_while_idle)
{
  init();

  tick(true, 30, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 30);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit(40);

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 30);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 10 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 30 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 40 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_lower_limit_while_idle_in_the_past)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit(40);

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 10 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 60);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 60 + count);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_lower_limit_while_active)
{
  init();

  tick(true, 30, [this](int count) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit(40);

  tick(true, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 30 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 40 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_lower_limit_to_in_the_past_while_active)
{
  init();

  tick(true, 100, [this](int count) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit(60);

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 40 + count);
    ASSERT_EQ(event, (count == 0 || count == 50) ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_raise_limit_while_idle)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit(160);

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 5 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 60, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 160 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_raise_limit_while_active)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit(160);

  tick(true, 60, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 160 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });
}

TEST_F(TimerTest, test_timer_lower_auto_reset_while_idle)
{
  init();

  tick(true, 100, [this](int count) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 10, [this](int count) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_auto_reset(15);

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 10 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(false, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 15 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_NATURAL_RESET : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_lower_auto_reset_to_in_the_past_while_idle)
{
  init();

  tick(true, 100, [this](int count) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 10, [this](int count) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_auto_reset(6);

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    // FIXME: why 6?
    ASSERT_EQ(timer->get_elapsed_idle_time(), 6 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_NATURAL_RESET : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_lower_auto_reset_while_active)
{
  init();

  tick(true, 100, [this](int count) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_auto_reset(10);

  tick(true, 40, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 140);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 40);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 10 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 40);
  });
}

TEST_F(TimerTest, test_timer_raise_auto_reset_while_idle)
{
  init();

  tick(true, 100, [this](int count) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 5, [this](int count) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_auto_reset(40);

  tick(false, 35, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 5 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 40 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_NATURAL_RESET : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_raise_auto_reset_while_active)
{
  init();

  tick(true, 50, [this](int count) {
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_auto_reset(40);

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 50 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(false, 40, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 100);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(false, 40, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 40 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_NATURAL_RESET : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_limit_reached_event)
{
  init();

  tick(false, 10, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  tick(true, 100, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  tick(true, 1, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_LIMIT_REACHED); });

  tick(true, 10, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });
}

TEST_F(TimerTest, test_timer_limit_reached_snoozed_event)
{
  init();

  tick(false, 10, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  tick(true, 100, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  tick(true, 1, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_LIMIT_REACHED); });

  tick(true, 49, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  tick(true, 1, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_LIMIT_REACHED); });

  // FIXME: why 49?
  tick(true, 49, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  tick(true, 1, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_LIMIT_REACHED); });

  tick(true, 49, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });
}

TEST_F(TimerTest, test_timer_limit_reached_snoozed_event_when_snoozed)
{
  init();

  tick(false, 10, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  tick(true, 100, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  tick(true, 1, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_LIMIT_REACHED); });

  tick(true, 40, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  timer->snooze_timer();

  tick(true, 49, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  tick(true, 1, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_LIMIT_REACHED); });

  tick(true, 30, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  timer->snooze_timer();

  // FIXME: why 49?
  tick(true, 49, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  tick(true, 1, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_LIMIT_REACHED); });

  tick(true, 49, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });
}

TEST_F(TimerTest, test_timer_limit_reached_inhibit_snooze_event)
{
  init();

  tick(false, 10, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  tick(true, 100, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  tick(true, 1, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_LIMIT_REACHED); });

  tick(true, 1, [](int count, TimerEvent event) { ASSERT_EQ(event, TIMER_EVENT_NONE); });

  timer->inhibit_snooze();

  tick(true, 200, [](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 48 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_disable_when_not_over_limit_and_active_then_enable_and_active)
{
  init();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->disable();
  timer->enable();
  timer->disable();
  timer->disable();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 49);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(false, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 49);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  timer->enable();
  timer->disable();
  timer->enable();
  timer->enable();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 49 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_disable_when_not_over_limit_and_active_then_enable_and_idle)
{
  init();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->disable();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 49);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(false, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 49);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  timer->enable();

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 19 ? TIMER_EVENT_NATURAL_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count < 19 ? 49 : 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 1 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });
}

TEST_F(TimerTest, test_timer_disable_when_not_over_limit_and_idle_then_enable_and_active)
{
  init();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 50);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->disable();

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 50);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0); // FIXME: 5
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 50);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  timer->enable();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 50 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_disable_when_not_over_limit_and_idle_then_enable_and_idle)
{
  init();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 50);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->disable();

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 50);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0); // FIXME: 5
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 50);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->enable();

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 19 ? TIMER_EVENT_NATURAL_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count < 19 ? 50 : 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 1 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });
}

TEST_F(TimerTest, test_timer_disable_when_over_limit_and_active_then_enable_and_active)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 100 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  timer->disable();

  tick(false, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 149);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 49);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 149);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 49);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  timer->enable();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 149 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 49 + count);
    ASSERT_EQ(event, (count == 0 || count == 50) ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
  });
}

TEST_F(TimerTest, test_timer_disable_when_over_limit_and_active_then_enable_and_idle)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 100 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  timer->disable();

  tick(false, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 149);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 49);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(timer->get_elapsed_time(), 149);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 49);
    ASSERT_EQ(event, TIMER_EVENT_NONE);
  });

  timer->enable();

  tick(false, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 19 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count < 19 ? 149 : 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 1 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 49);
  });
}

TEST_F(TimerTest, test_timer_disable_when_over_limit_and_idle_then_enable_and_active)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 100 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  timer->disable();

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  tick(false, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  timer->enable();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, (count == 0 || count == 50) ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 50 + count);
  });
}

TEST_F(TimerTest, test_timer_disable_when_over_limit_and_idle_then_enable_and_idle)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 100 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  timer->disable();

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0); // FIXME: why reset?
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  tick(false, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  timer->enable();

  tick(false, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 19 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count < 19 ? 150 : 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 1 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });
}

TEST_F(TimerTest, test_timer_enable_limit_when_active_and_not_overdue)
{
  init();

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 20 ? TIMER_EVENT_NATURAL_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count < 20 ? 20 + count : count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit_enabled(false);

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  ASSERT_EQ(timer->get_total_overdue_time(), 0);
  timer->set_limit_enabled(true);
  ASSERT_EQ(timer->get_total_overdue_time(), 0);

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 50 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });
}

TEST_F(TimerTest, test_timer_enable_limit_when_active_and_overdue)
{
  init();

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 20 ? TIMER_EVENT_NATURAL_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count < 20 ? 20 + count : count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit_enabled(false);

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  ASSERT_EQ(timer->get_total_overdue_time(), 0);
  timer->set_limit_enabled(true);
  ASSERT_EQ(timer->get_total_overdue_time(), 49);

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, (count == 0 || count == 50) ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 50 + count);
  });
}

TEST_F(TimerTest, test_timer_enable_limit_when_idle)
{
  init();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit_enabled(false);

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 20 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count < 20 ? 50 : 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit_enabled(true);

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 50 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });
}

TEST_F(TimerTest, test_timer_disable_limit_while_active_and_not_overdue)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 100 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 101, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count % 50 == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  ASSERT_EQ(timer->get_total_overdue_time(), 100);

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 20 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count < 20 ? 201 : 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 101);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 100 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 101);
  });

  timer->set_limit_enabled(false);

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 50 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 101);
  });
}

TEST_F(TimerTest, test_timer_disable_limit_when_idle)
{
  init();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 20 ? TIMER_EVENT_NATURAL_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count < 20 ? 50 : 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit_enabled(false);

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 50 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });
}

TEST_F(TimerTest, test_timer_disable_limit_when_active)
{
  init();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit_enabled(false);

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 50 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });
}

TEST_F(TimerTest, test_timer_disable_limit_while_active_and_overdue)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 100 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 101, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count % 50 == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  ASSERT_EQ(timer->get_total_overdue_time(), 100);

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 20 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count < 20 ? 201 : 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 101);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 100 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 101);
  });

  tick(true, 101, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count % 50 == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 101 + count);
  });

  // FIXME: first loop of 201 step incresaes overdue time by 100.
  // FIXME: second loop incresaes overdue time by 101. Why?
  ASSERT_EQ(timer->get_total_overdue_time(), 201);

  timer->set_limit_enabled(false);

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 201 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 101);
  });

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 301);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 101);
  });
}

TEST_F(TimerTest, test_timer_disable_auto_reset)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 100 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  timer->set_auto_reset_enabled(false);

  tick(false, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 10 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });
}

TEST_F(TimerTest, test_timer_disable_limit_when_timer_is_disabled)
{
  init();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 20 ? TIMER_EVENT_NATURAL_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count < 20 ? 50 : 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->disable();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 20);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  timer->set_limit_enabled(false);

  timer->enable();

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 20 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });
}

TEST_F(TimerTest, test_timer_toggle_auto_reset__enable_after_reset)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 100 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  timer->set_auto_reset_enabled(false);

  tick(false, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 10 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  timer->set_auto_reset_enabled(true);

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    // FIXME: why is this set to 20 instead of 60?
    ASSERT_EQ(timer->get_elapsed_idle_time(), 20 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });
}

TEST_F(TimerTest, test_timer_toggle_auto_reset_before_reset)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 100 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  timer->set_auto_reset_enabled(false);

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 10 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  timer->set_auto_reset_enabled(true);

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 15 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 20 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });
}

TEST_F(TimerTest, test_timer_toggle_auto_reset_when_timer_is_disabled)
{
  init();

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 100 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_LIMIT_REACHED : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), count);
  });

  tick(false, 10, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  timer->disable();

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  timer->set_auto_reset_enabled(false);
  timer->enable();

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 1 + count); // FIXME: should 10+count
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  timer->set_auto_reset_enabled(true);

  tick(false, 14, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 150);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 6 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 0);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 20 + count);
    ASSERT_EQ(timer->get_total_overdue_time(), 50);
  });
}

TEST_F(TimerTest, test_timer_daily_reset_with_predicate)
{
  init();

  TestTimePred *p = new TestTimePred;
  p->set(TimeSource::get_real_time_sec_sync() + 371);
  timer->set_limit(4 * 3600);
  timer->set_auto_reset(0);
  timer->set_auto_reset_enabled(false);
  timer->set_auto_reset(p);

  tick(true, 100, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 200, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 100 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 21, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 300);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 300 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  // reset

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 50 + count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, count == 0 ? TIMER_EVENT_RESET : TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });
}

TEST_F(TimerTest, test_timer_serialize)
{
  init();

  tick(true, 50, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), count);
    ASSERT_EQ(timer->get_elapsed_idle_time(), 0);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  tick(false, 5, [this](int count, TimerEvent event) {
    ASSERT_EQ(event, TIMER_EVENT_NONE);
    ASSERT_EQ(timer->get_elapsed_time(), 50);
    ASSERT_EQ(timer->get_elapsed_idle_time(), count);
    ASSERT_EQ(timer->get_total_overdue_time(), 0);
  });

  std::string s1 = timer->serialize_state();
  s1 = s1.substr(s1.find_first_of(" ") + 1);

  Timer::Ptr t = make_timer();

  bool b = t->deserialize_state(s1, 3);
  ASSERT_EQ(b, true);

  std::string s2 = t->serialize_state();
  s2 = s2.substr(s2.find_first_of(" ") + 1);
  ASSERT_EQ(s1, s2);
}

TEST_F(TimerTest, test_timer_noop_controls)
{
  init();

  timer->start_timer();
  timer->start_timer();

  timer->disable();
  timer->snooze_timer();

  ASSERT_TRUE(!timer->is_enabled());
}

TEST_F(TimerTest, test_timer_zero_limit_and_reset_without_auto_reset)
{
  init();

  timer->set_limit(0);
  ASSERT_EQ(timer->get_limit(), 0);

  timer->set_auto_reset_enabled(false);
  timer->reset_timer();

  ASSERT_EQ(timer->get_elapsed_time(), 0);
}

TEST_F(TimerTest, test_timer_deserialize_version_two_with_future_reset)
{
  init();

  const auto now = sim->get_real_time_usec() / 1000000;
  timer->set_auto_reset_enabled(false);

  std::ostringstream state;
  state << now << " 10 " << now + 1 << " 3 0 0 0";

  ASSERT_TRUE(timer->deserialize_state(state.str(), 2));
  ASSERT_EQ(timer->get_elapsed_time(), 10);
  ASSERT_EQ(timer->get_total_overdue_time(), 3);
}

TEST_F(TimerTest, test_timer_deserialize_too_old_state)
{
  init();

  const auto now = sim->get_real_time_usec() / 1000000;
  std::ostringstream state;
  state << now - 100 << " 10 " << now - 100 << " 0 0 0 0 0";

  ASSERT_TRUE(timer->deserialize_state(state.str(), 3));
  ASSERT_EQ(timer->get_elapsed_time(), 0);
}

TEST_F(TimerTest, test_timer_deserialize_overdue_state)
{
  init();

  const auto now = sim->get_real_time_usec() / 1000000;
  std::ostringstream state;
  state << now << " 150 " << now << " 0 0 0 120 0";

  ASSERT_TRUE(timer->deserialize_state(state.str(), 3));
  ASSERT_EQ(timer->get_elapsed_time(), 150);
  ASSERT_EQ(timer->get_total_overdue_time(), 50);
}

