// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "quiche/quic/core/quic_connection_context.h"

#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/platform/api/quic_thread.h"

using testing::ElementsAre;

namespace quic::test {
namespace {

class TraceCollector : public QuicConnectionTracer {
 public:
  ~TraceCollector() override = default;

  void PrintLiteral(const char* literal) override { trace_.push_back(literal); }

  void PrintString(absl::string_view s) override {
    trace_.push_back(std::string(s));
  }

  const std::vector<std::string>& trace() const { return trace_; }

 private:
  std::vector<std::string> trace_;
};

struct FakeConnection {
  FakeConnection() { context.tracer = std::make_unique<TraceCollector>(); }

  const std::vector<std::string>& trace() const {
    return static_cast<const TraceCollector*>(context.tracer.get())->trace();
  }

  QuicConnectionContext context;
};

void SimpleSwitch() {
  FakeConnection connection;

  // These should be ignored since current context is nullptr.
  EXPECT_EQ(QuicConnectionContext::Current(), nullptr);
  QUIC_TRACELITERAL("before switch: literal");
  QUIC_TRACESTRING(std::string("before switch: string"));
  QUIC_TRACEPRINTF("%s: %s", "before switch", "printf");

  {
    QuicConnectionContextSwitcher switcher(&connection.context);
    QUIC_TRACELITERAL("literal");
    QUIC_TRACESTRING(std::string("string"));
    QUIC_TRACEPRINTF("%s", "printf");
  }

  EXPECT_EQ(QuicConnectionContext::Current(), nullptr);
  QUIC_TRACELITERAL("after switch: literal");
  QUIC_TRACESTRING(std::string("after switch: string"));
  QUIC_TRACEPRINTF("%s: %s", "after switch", "printf");

  EXPECT_THAT(connection.trace(), ElementsAre("literal", "string", "printf"));
}

void NestedSwitch() {
  FakeConnection outer, inner;

  {
    QuicConnectionContextSwitcher switcher(&outer.context);
    QUIC_TRACELITERAL("outer literal 0");
    QUIC_TRACESTRING(std::string("outer string 0"));
    QUIC_TRACEPRINTF("%s %s %d", "outer", "printf", 0);

    {
      QuicConnectionContextSwitcher nested_switcher(&inner.context);
      QUIC_TRACELITERAL("inner literal");
      QUIC_TRACESTRING(std::string("inner string"));
      QUIC_TRACEPRINTF("%s %s", "inner", "printf");
    }

    QUIC_TRACELITERAL("outer literal 1");
    QUIC_TRACESTRING(std::string("outer string 1"));
    QUIC_TRACEPRINTF("%s %s %d", "outer", "printf", 1);
  }

  EXPECT_THAT(outer.trace(), ElementsAre("outer literal 0", "outer string 0",
                                         "outer printf 0", "outer literal 1",
                                         "outer string 1", "outer printf 1"));

  EXPECT_THAT(inner.trace(),
              ElementsAre("inner literal", "inner string", "inner printf"));
}

void AlternatingSwitch() {
  FakeConnection zero, one, two;
  for (int i = 0; i < 15; ++i) {
    FakeConnection* connection =
        ((i % 3) == 0) ? &zero : (((i % 3) == 1) ? &one : &two);

    QuicConnectionContextSwitcher switcher(&connection->context);
    QUIC_TRACEPRINTF("%d", i);
  }

  EXPECT_THAT(zero.trace(), ElementsAre("0", "3", "6", "9", "12"));
  EXPECT_THAT(one.trace(), ElementsAre("1", "4", "7", "10", "13"));
  EXPECT_THAT(two.trace(), ElementsAre("2", "5", "8", "11", "14"));
}

typedef void (*ThreadFunction)();

template <ThreadFunction func>
class TestThread : public QuicThread {
 public:
  TestThread() : QuicThread("TestThread") {}
  ~TestThread() override = default;

 protected:
  void Run() override { func(); }
};

template <ThreadFunction func>
void RunInThreads(size_t n_threads) {
  using ThreadType = TestThread<func>;
  std::vector<ThreadType> threads(n_threads);

  for (ThreadType& t : threads) {
    t.Start();
  }

  for (ThreadType& t : threads) {
    t.Join();
  }
}

class QuicConnectionContextTest : public QuicTest {
 protected:
};

TEST_F(QuicConnectionContextTest, NullTracerOK) {
  FakeConnection connection;
  std::unique_ptr<QuicConnectionTracer> tracer;

  {
    QuicConnectionContextSwitcher switcher(&connection.context);
    QUIC_TRACELITERAL("msg 1 recorded");
  }

  connection.context.tracer.swap(tracer);

  {
    QuicConnectionContextSwitcher switcher(&connection.context);
    // Should be a no-op since connection.context.tracer is nullptr.
    QUIC_TRACELITERAL("msg 2 ignored");
  }

  EXPECT_THAT(static_cast<TraceCollector*>(tracer.get())->trace(),
              ElementsAre("msg 1 recorded"));
}

TEST_F(QuicConnectionContextTest, TestSimpleSwitch) {
  RunInThreads<SimpleSwitch>(10);
}

TEST_F(QuicConnectionContextTest, TestNestedSwitch) {
  RunInThreads<NestedSwitch>(10);
}

TEST_F(QuicConnectionContextTest, TestAlternatingSwitch) {
  RunInThreads<AlternatingSwitch>(10);
}

}  // namespace
}  // namespace quic::test
