TEST(EventTests, waitIsWaiting) {
  Dispatcher dispatcher;
  Event event(dispatcher);
  bool done = false;
  Context<> context(dispatcher, [&]() {
    event.wait();
    done = true;
  });

  dispatcher.yield();
  ASSERT_FALSE(done);
  event.set();
  dispatcher.yield();
  ASSERT_TRUE(done);
}
TEST(ContextGroupTests, ContextGroupIsWaitingNestedSpawnsEvenThoughItWasInterrupted) {
  Dispatcher dispatcher;

  bool contextFinished = false;
  bool nestedContextFinished = false;

  {
    ContextGroup cg1(dispatcher);
    cg1.spawn([&] {
      try {
        Timer(dispatcher).sleep(std::chrono::milliseconds(100));
        contextFinished = true;
      } catch (InterruptedException&) {
        cg1.spawn([&] {
          try {
            Timer(dispatcher).sleep(std::chrono::milliseconds(100));
            nestedContextFinished = true;
          } catch (InterruptedException&) {
          }
        });
      }
    });

    dispatcher.yield();
  }

  ASSERT_FALSE(contextFinished);
  ASSERT_TRUE(nestedContextFinished);
}
TEST(EventLockTests, eventLockIsLocking) {
  Dispatcher dispatcher;
  Event event(dispatcher);
  bool done = false;
  Context<> context(dispatcher, [&]() {
    EventLock lock(event);
    done = true;
  });

  ASSERT_FALSE(done);
  dispatcher.yield();
  ASSERT_FALSE(done);
  event.set();
  dispatcher.yield();
  ASSERT_TRUE(done);
}
TEST(ContextGroupTests, ConnectionWriteIsThrowingWhenCurrentContextIsInterrupted) {
  Dispatcher dispatcher;

  bool interrupted = false;
  {
    Event connected(dispatcher);
    ContextGroup cg1(dispatcher);
    cg1.spawn([&] {
      try {
        auto conn = TcpListener(dispatcher, Ipv4Address("0.0.0.0"), 12345).accept();
        conn.write(nullptr, 0);
      } catch (InterruptedException&) {
      }
    });

    cg1.spawn([&] {
      try {
        auto conn = TcpConnector(dispatcher).connect(Ipv4Address("127.0.0.1"), 12345);
        connected.set();
        dispatcher.yield();
        conn.write(nullptr, 0);
      } catch (InterruptedException&) {
        interrupted = true;
      }
    });

    connected.wait();
  }

  ASSERT_TRUE(interrupted);
}
TEST(ContextGroupTests, ConnectionReadIsContextIntrerruptible) {
  Dispatcher dispatcher;

  bool interrupted = false;
  {
    Event connected(dispatcher);
    ContextGroup cg1(dispatcher);
    cg1.spawn([&] {
      try {
        auto conn = TcpListener(dispatcher, Ipv4Address("0.0.0.0"), 12345).accept();
        Timer(dispatcher).sleep(std::chrono::milliseconds(1000));
        conn.write(nullptr, 0);
      } catch (InterruptedException&) {
      }
    });

    cg1.spawn([&] {
      try {
        auto conn = TcpConnector(dispatcher).connect(Ipv4Address("127.0.0.1"), 12345);
        connected.set();
        uint8_t a[10];
        conn.read(a, 10);
        conn.write(nullptr, 0);
      } catch (InterruptedException&) {
        interrupted = true;
      }
    });

    connected.wait();
    dispatcher.yield();
  }

  ASSERT_TRUE(interrupted);
}
TEST(EventTests, setEventIsNotWaiting) {
  Dispatcher dispatcher;
  Event event(dispatcher);
  auto i = 0;
  Context<> context(dispatcher, [&]() {
    event.set();
    dispatcher.yield();
    i++;
  });

  event.wait();
  i++;
  ASSERT_EQ(i, 1);
  event.wait();
  ASSERT_EQ(i, 1);
  dispatcher.yield();
  ASSERT_EQ(i, 2);
}
TEST(EventTests, setSetsOnlyOnce) {
  Dispatcher dispatcher;
  Event event(dispatcher);
  auto i = 0;
  Context<> context(dispatcher, [&]() {
    event.set();
    event.set();
    event.set();
    dispatcher.yield();
    i++;
  });

  event.wait();
  i++;
  event.wait();
  ASSERT_EQ(i, 1);
  dispatcher.yield();
  ASSERT_EQ(i, 2);
}
TEST(EventTests, setEventInPastUnblocksWaitersEvenAfterClear) {
  Dispatcher dispatcher;
  Event event(dispatcher);
  auto i = 0;
  Context<> context(dispatcher, [&]() {
    event.wait();
    i++;
  });

  Context<> contextSecond(dispatcher, [&]() {
    event.wait();
    i++;
  });

  dispatcher.yield();
  ASSERT_EQ(i, 0);
  event.set();
  event.clear();
  dispatcher.yield();
  ASSERT_EQ(i, 2);
}
TEST(EventTests, waitIsMultispawn) {
  Dispatcher dispatcher;
  Event event(dispatcher);
  auto i = 0;
  Context<> context(dispatcher, [&]() {
    event.wait();
    i++;
  });

  Context<> contextSecond(dispatcher, [&]() {
    event.wait();
    i++;
  });

  ASSERT_EQ(i, 0);
  dispatcher.yield();
  ASSERT_EQ(i, 0);
  event.set();
  dispatcher.yield();
  ASSERT_EQ(i, 2);
}
TEST(EventLockTests, eventLockIsUnlockOnlyOnce) {
  Dispatcher dispatcher;
  Event event(dispatcher);
  auto i = 0;
  Context<> context(dispatcher, [&]() {
    EventLock lock(event);
    i++;
    dispatcher.yield();
    i++;
  });

  Context<> contextSecond(dispatcher, [&]() {
    EventLock lock(event);
    i += 2;
    dispatcher.yield();
    i += 2;
  });
  
  event.set();
  dispatcher.yield();
  ASSERT_EQ(i, 1);
  dispatcher.yield();
  ASSERT_EQ(i, 2);
  dispatcher.yield();
  ASSERT_EQ(i, 4);
  dispatcher.yield();
  ASSERT_EQ(i, 6);
}
TEST(ContextGroupTests, ContextGroupWaitIsWaiting) {
  Dispatcher dispatcher;

  bool contextFinished = false;
  ContextGroup cg1(dispatcher);
  cg1.spawn([&] {
    dispatcher.yield();
    contextFinished = true;
  });

  cg1.wait();
  ASSERT_TRUE(contextFinished);
}
TEST(EventTests, eventIsReusableAfterClear) {
  Dispatcher dispatcher;
  Event event(dispatcher);
  Context<> context(dispatcher, [&]() {
    event.set();
    dispatcher.yield();
    event.set();
  });

  event.wait();
  event.clear();
  event.wait();
  SUCCEED();
}
TEST(ContextGroupTests, DispatcherInterruptSetsFlag) {
  Dispatcher dispatcher;
  Context<> context(dispatcher, [&] {
    try {
      Timer(dispatcher).sleep(std::chrono::milliseconds(10));
    } catch (InterruptedException&) {
    }
  });

  dispatcher.interrupt();
  dispatcher.yield();
  ASSERT_TRUE(dispatcher.interrupted());
  ASSERT_FALSE(dispatcher.interrupted());
}
TEST(ContextGroupTests, testHangingUp) {
  Dispatcher dispatcher;
  Event e(dispatcher);
  Context<> context(dispatcher, [&] {
    Timer(dispatcher).sleep(std::chrono::milliseconds(100));
  });

  Context<> contextSecond(dispatcher, [&] {
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    e.set();
    dispatcher.yield();
  });

  e.wait();
}
TEST(ContextGroupTests, DispatcherInterruptIsInterrupting) {
  bool interrupted = false;
  {
    Dispatcher dispatcher;
    Context<> context(dispatcher, [&] {
      try {
        Timer(dispatcher).sleep(std::chrono::milliseconds(1000));
      } catch (InterruptedException&) {
        interrupted = true;
      }
    });

    dispatcher.yield();
  }

  ASSERT_TRUE(interrupted);
}
TEST(ContextGroupTests, ConnectorConnectIsContextIntrerruptible) {
  Dispatcher dispatcher;

  bool interrupted = false;
  {
    ContextGroup cg1(dispatcher);
    cg1.spawn([&] {
      try {
        TcpConnector(dispatcher).connect(Ipv4Address("127.0.0.1"), 12345);
      } catch (InterruptedException&) {
        interrupted = true;
      }
    });

    dispatcher.yield();
  }
  ASSERT_TRUE(interrupted);
}
TEST(ContextGroupTests, ListenerAcceptIsContextIntrerruptible) {
  Dispatcher dispatcher;

  bool interrupted = false;
  {
    ContextGroup cg1(dispatcher);
    cg1.spawn([&] {
      try {
        TcpListener(dispatcher, Ipv4Address("0.0.0.0"), 12345).accept();
      } catch (InterruptedException&) {
        interrupted = true;
      }
    });

    dispatcher.yield();
  }

  ASSERT_TRUE(interrupted);
}
TEST(ContextGroupTests, TimerIsContextIntrerruptible) {
  Dispatcher dispatcher;
  
  bool interrupted = false;
  {
    ContextGroup cg1(dispatcher);
    cg1.spawn([&] {
      try {
        Timer(dispatcher).sleep(std::chrono::milliseconds(1000));
      } catch (InterruptedException&) {
        interrupted = true;
      }
    });

    dispatcher.yield();
  }

  ASSERT_TRUE(interrupted);
}