//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).
//

#include "logging/env_logger.h"
#include "env/mock_env.h"
#include "test_util/testharness.h"
#include "test_util/testutil.h"

namespace ROCKSDB_NAMESPACE {

namespace {
// In this test we only want to Log some simple log message with
// no format.
void LogMessage(std::shared_ptr<Logger> logger, const std::string& message) {
  Log(logger, "%s", message.c_str());
}

// Helper method to write the message num_times in the given logger.
void WriteLogs(std::shared_ptr<Logger> logger, const std::string& message,
               int num_times) {
  for (int ii = 0; ii < num_times; ++ii) {
    LogMessage(logger, message);
  }
}

}  // namespace

class EnvLoggerTest : public testing::Test {
 public:
  Env* env_;

  EnvLoggerTest() : env_(Env::Default()) {}

  ~EnvLoggerTest() = default;

  std::shared_ptr<Logger> CreateLogger() {
    std::shared_ptr<Logger> result;
    assert(NewEnvLogger(kLogFile, env_, &result).ok());
    assert(result);
    result->SetInfoLogLevel(InfoLogLevel::INFO_LEVEL);
    return result;
  }

  void DeleteLogFile() { ASSERT_OK(env_->DeleteFile(kLogFile)); }

  static const std::string kSampleMessage;
  static const std::string kTestDir;
  static const std::string kLogFile;
};

const std::string EnvLoggerTest::kSampleMessage =
    "this is the message to be written to the log file!!";
const std::string EnvLoggerTest::kLogFile = test::PerThreadDBPath("log_file");

TEST_F(EnvLoggerTest, EmptyLogFile) {
  auto logger = CreateLogger();
  ASSERT_EQ(logger->Close(), Status::OK());

  // Check the size of the log file.
  uint64_t file_size;
  ASSERT_EQ(env_->GetFileSize(kLogFile, &file_size), Status::OK());
  ASSERT_EQ(file_size, 0);
  DeleteLogFile();
}

TEST_F(EnvLoggerTest, LogMultipleLines) {
  auto logger = CreateLogger();

  // Write multiple lines.
  const int kNumIter = 10;
  WriteLogs(logger, kSampleMessage, kNumIter);

  // Flush the logs.
  logger->Flush();
  ASSERT_EQ(logger->Close(), Status::OK());

  // Validate whether the log file has 'kNumIter' number of lines.
  ASSERT_EQ(test::GetLinesCount(kLogFile, kSampleMessage), kNumIter);
  DeleteLogFile();
}

TEST_F(EnvLoggerTest, Overwrite) {
  {
    auto logger = CreateLogger();

    // Write multiple lines.
    const int kNumIter = 10;
    WriteLogs(logger, kSampleMessage, kNumIter);

    ASSERT_EQ(logger->Close(), Status::OK());

    // Validate whether the log file has 'kNumIter' number of lines.
    ASSERT_EQ(test::GetLinesCount(kLogFile, kSampleMessage), kNumIter);
  }

  // Now reopen the file again.
  {
    auto logger = CreateLogger();

    // File should be empty.
    uint64_t file_size;
    ASSERT_EQ(env_->GetFileSize(kLogFile, &file_size), Status::OK());
    ASSERT_EQ(file_size, 0);
    ASSERT_EQ(logger->GetLogFileSize(), 0);
    ASSERT_EQ(logger->Close(), Status::OK());
  }
  DeleteLogFile();
}

TEST_F(EnvLoggerTest, Close) {
  auto logger = CreateLogger();

  // Write multiple lines.
  const int kNumIter = 10;
  WriteLogs(logger, kSampleMessage, kNumIter);

  ASSERT_EQ(logger->Close(), Status::OK());

  // Validate whether the log file has 'kNumIter' number of lines.
  ASSERT_EQ(test::GetLinesCount(kLogFile, kSampleMessage), kNumIter);
  DeleteLogFile();
}

TEST_F(EnvLoggerTest, ConcurrentLogging) {
  auto logger = CreateLogger();

  const int kNumIter = 20;
  std::function<void()> cb = [&]() {
    WriteLogs(logger, kSampleMessage, kNumIter);
    logger->Flush();
  };

  // Write to the logs from multiple threads.
  std::vector<port::Thread> threads;
  const int kNumThreads = 5;
  // Create threads.
  for (int ii = 0; ii < kNumThreads; ++ii) {
    threads.push_back(port::Thread(cb));
  }

  // Wait for them to complete.
  for (auto& th : threads) {
    th.join();
  }

  ASSERT_EQ(logger->Close(), Status::OK());

  // Verfiy the log file.
  ASSERT_EQ(test::GetLinesCount(kLogFile, kSampleMessage),
            kNumIter * kNumThreads);
  DeleteLogFile();
}

}  // namespace ROCKSDB_NAMESPACE

int main(int argc, char** argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
