#include "klee/System/Time.h"
#include "gtest/gtest.h"
#include "gtest/gtest-death-test.h"

#include <cerrno>
#include <sstream>


int finished = 0;

using namespace klee;


TEST(TimeTest, TimingFunctions) {
  auto t = time::getClockInfo();
  ASSERT_STRNE(t.c_str(), "");

  auto p0 = time::getWallTime();
  auto p1 = time::getWallTime();
  ASSERT_GT(p0, time::Point());
  ASSERT_GT(p1, time::Point());
  ASSERT_LE(p0, p1);

  time::getUserTime();
}


TEST(TimeTest, TimeParserNewFormat) {
  // valid
  errno = 0;
  auto s0 = time::Span("");
  ASSERT_EQ(s0, time::Span());
  ASSERT_EQ(errno, 0);

  s0 = time::Span("3h10min");
  ASSERT_EQ(s0, time::minutes(190));
  s0 = time::Span("5min5min");
  ASSERT_EQ(s0, time::seconds(600));
  s0 = time::Span("3us");
  ASSERT_EQ(s0, time::microseconds(3));
  s0 = time::Span("1h1min1s1ms1us1ns");
  ASSERT_EQ(s0, time::nanoseconds(3661001001001));
  s0 = time::Span("1min1min");
  ASSERT_EQ(s0, time::minutes(2));

  // invalid
  ASSERT_EXIT(time::Span("h"), ::testing::ExitedWithCode(1), "Illegal number format: h");
  ASSERT_EXIT(time::Span("-5min"), ::testing::ExitedWithCode(1), "Illegal number format: -5min");
  ASSERT_EXIT(time::Span("3.5h"), ::testing::ExitedWithCode(1), "Illegal number format: 3.5h");
  ASSERT_EXIT(time::Span("3mi"), ::testing::ExitedWithCode(1), "Illegal number format: 3mi");
}


TEST(TimeTest, TimeParserOldFormat) {
  // valid
  errno = 0;

  auto s0 = time::Span("20");
  ASSERT_EQ(s0, time::seconds(20));
  s0 = time::Span("3.5");
  ASSERT_EQ(s0, time::milliseconds(3500));
  s0 = time::Span("0.0");
  ASSERT_EQ(s0, time::Span());
  s0 = time::Span("0");
  ASSERT_EQ(s0, time::Span());

  ASSERT_EQ(errno, 0);

  // invalid
  ASSERT_EXIT(time::Span("-3.5"), ::testing::ExitedWithCode(1), "Illegal number format: -3.5");
  ASSERT_EXIT(time::Span("NAN"), ::testing::ExitedWithCode(1), "Illegal number format: NAN");
  ASSERT_EXIT(time::Span("INF"), ::testing::ExitedWithCode(1), "Illegal number format: INF");
  ASSERT_EXIT(time::Span("foo"), ::testing::ExitedWithCode(1), "Illegal number format: foo");
}


TEST(TimeTest, TimeArithmeticAndComparisons) {
  auto h   = time::hours(1);
  auto min = time::minutes(1);
  auto sec = time::seconds(1);
  auto ms  = time::milliseconds(1);
  auto us  = time::microseconds(1);
  auto ns  = time::nanoseconds(1);

  ASSERT_GT(h, min);
  ASSERT_GT(min, sec);
  ASSERT_GT(sec, ms);
  ASSERT_GT(ms, us);
  ASSERT_GT(us, ns);

  ASSERT_LT(min, h);
  ASSERT_LT(sec, min);
  ASSERT_LT(ms, sec);
  ASSERT_LT(us, ms);
  ASSERT_LT(ns, us);

  ASSERT_GE(h, min);
  ASSERT_LE(min, h);

  auto sec2 = time::seconds(1);
  ASSERT_EQ(sec, sec2);
  sec2 += time::nanoseconds(1);
  ASSERT_LT(sec, sec2);

  auto op0 = time::seconds(1);
  auto op1 = op0 / 1000U;
  ASSERT_EQ(op1, ms);
  op0 = time::nanoseconds(3);
  op1 = op0 * 1000U;
  ASSERT_EQ(op1, 3U*us);

  op0 = (time::seconds(10) + time::minutes(1) - time::milliseconds(10000)) * 60U;
  ASSERT_EQ(op0, h);

  auto p1 = time::getWallTime();
  auto p2 = p1;
  p1 += time::seconds(10);
  p2 -= time::seconds(15);
  ASSERT_EQ(p1 - p2, time::seconds(25));

  auto s = time::minutes(3);
  p1 = s + p2;
  ASSERT_NE(p1, p2);
  ASSERT_LT(p2, p1);
  p1 = p1 - s;
  ASSERT_EQ(p1, p2);

  s = time::minutes(5);
  s -= time::minutes(4);
  ASSERT_EQ(s, time::seconds(60));
}


TEST(TimeTest, TimeConversions) {
  auto t = time::Span("3h14min1s");
  auto d = t.toSeconds();
  ASSERT_EQ(d, 11641.0);

  std::uint32_t h;
  std::uint8_t m, s;
  std::tie(h, m, s) = t.toHMS();
  ASSERT_EQ(h, 3u);
  ASSERT_EQ(m, 14u);
  ASSERT_EQ(s, 1u);

  ASSERT_TRUE((bool)t);
  ASSERT_FALSE((bool)time::Span());

  auto us = t.toMicroseconds();
  ASSERT_EQ(us, 11641000000u);

  t += time::microseconds(42);
  auto v = (timeval)t;
  ASSERT_EQ(v.tv_sec, 11641);
  ASSERT_EQ(v.tv_usec, 42);

  t = std::chrono::seconds(1);
  ASSERT_EQ(t, time::seconds(1));
  auto u = (std::chrono::steady_clock::duration)t;
  ASSERT_EQ(u, std::chrono::seconds(1));

  std::ostringstream os;
  os << time::Span("2.5");
  ASSERT_EQ(os.str(), "2.5s");
}

TEST(TimeTest, ImplicitArithmeticConversions) {
  auto t1 = time::Span("1000s");
  t1 *= 2U;
  auto d = t1.toSeconds();
  ASSERT_EQ(d, 2000.0);

  auto t2 = t1 * 1.5;
  d = t2.toSeconds();
  ASSERT_EQ(d, 3000.0);

  t2 = 2.5 * t1;
  d = t2.toSeconds();
  ASSERT_EQ(d, 5000.0);

  t1 = time::Span("1000s");
  t1 *= 2.2;
  d = t1.toSeconds();
  ASSERT_EQ(d, 2200.0);
}
