// Copyright (c) 2009 The Chromium Embedded Framework 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 <algorithm>

#include "include/cef_stream.h"
#include "tests/gtest/include/gtest/gtest.h"

static void VerifyStreamReadBehavior(CefRefPtr<CefStreamReader> stream,
                                     const std::string& contents) {
  int contentSize = static_cast<int>(contents.size());
  const char* contentStr = contents.c_str();

  // Move to the beginning of the stream
  ASSERT_EQ(0, stream->Seek(0, SEEK_SET));
  ASSERT_EQ(0, stream->Tell());

  // Move to the end of the stream
  ASSERT_EQ(0, stream->Seek(0, SEEK_END));
  ASSERT_EQ(contentSize, stream->Tell());

  // Move to the beginning of the stream
  ASSERT_EQ(0, stream->Seek(-contentSize, SEEK_CUR));
  ASSERT_EQ(0, stream->Tell());

  // Read 10 characters at a time and verify the result
  char buff[10];
  int res, read, offset = 0;
  do {
    read = std::min(static_cast<int>(sizeof(buff)), contentSize - offset);
    res = static_cast<int>(stream->Read(buff, 1, read));
    ASSERT_EQ(read, res);
    ASSERT_TRUE(!memcmp(contentStr + offset, buff, res));
    offset += res;
  } while (offset < contentSize);

  // Read past the end of the file
  stream->Read(buff, 1, 1);
  ASSERT_TRUE(stream->Eof());
}

static void VerifyStreamWriteBehavior(CefRefPtr<CefStreamWriter> stream,
                                      const std::string& contents) {
  int contentSize = static_cast<int>(contents.size());
  const char* contentStr = contents.c_str();

  // Write 10 characters at a time and verify the result
  int res, write, offset = 0;
  do {
    write = std::min(10, contentSize - offset);
    res = static_cast<int>(stream->Write(contentStr + offset, 1, write));
    ASSERT_EQ(write, res);
    offset += res;
    ASSERT_EQ(offset, stream->Tell());
  } while (offset < contentSize);

  // Move to the beginning of the stream
  ASSERT_EQ(0, stream->Seek(-contentSize, SEEK_CUR));
  ASSERT_EQ(0, stream->Tell());

  // Move to the end of the stream
  ASSERT_EQ(0, stream->Seek(0, SEEK_END));
  ASSERT_EQ(contentSize, stream->Tell());

  // Move to the beginning of the stream
  ASSERT_EQ(0, stream->Seek(0, SEEK_SET));
  ASSERT_EQ(0, stream->Tell());
}

TEST(StreamTest, ReadFile) {
  const char* fileName = "StreamTest.VerifyReadFile.txt";
  CefString fileNameStr = "StreamTest.VerifyReadFile.txt";
  std::string contents = "This is my test\ncontents for the file";

  // Create the file
  FILE* f = nullptr;
#ifdef _WIN32
  fopen_s(&f, fileName, "wb");
#else
  f = fopen(fileName, "wb");
#endif
  ASSERT_TRUE(f != nullptr);
  ASSERT_EQ((size_t)1, fwrite(contents.c_str(), contents.size(), 1, f));
  fclose(f);

  // Test the stream
  CefRefPtr<CefStreamReader> stream(
      CefStreamReader::CreateForFile(fileNameStr));
  ASSERT_TRUE(stream.get() != nullptr);
  ASSERT_TRUE(stream->MayBlock());
  VerifyStreamReadBehavior(stream, contents);

  // Release the file pointer
  stream = nullptr;

// Delete the file
#ifdef _WIN32
  ASSERT_EQ(0, _unlink(fileName));
#else
  ASSERT_EQ(0, unlink(fileName));
#endif
}

TEST(StreamTest, ReadData) {
  std::string contents = "This is my test\ncontents for the file";

  // Test the stream
  CefRefPtr<CefStreamReader> stream(CefStreamReader::CreateForData(
      static_cast<void*>(const_cast<char*>(contents.c_str())),
      contents.size()));
  ASSERT_TRUE(stream.get() != nullptr);
  ASSERT_FALSE(stream->MayBlock());
  VerifyStreamReadBehavior(stream, contents);
}

TEST(StreamTest, WriteFile) {
  const char* fileName = "StreamTest.VerifyWriteFile.txt";
  CefString fileNameStr = "StreamTest.VerifyWriteFile.txt";
  std::string contents = "This is my test\ncontents for the file";

  // Test the stream
  CefRefPtr<CefStreamWriter> stream(
      CefStreamWriter::CreateForFile(fileNameStr));
  ASSERT_TRUE(stream.get() != nullptr);
  ASSERT_TRUE(stream->MayBlock());
  VerifyStreamWriteBehavior(stream, contents);

  // Release the file pointer
  stream = nullptr;

  // Read the file that was written
  FILE* f = nullptr;
  char* buff = new char[contents.size()];
#ifdef _WIN32
  fopen_s(&f, fileName, "rb");
#else
  f = fopen(fileName, "rb");
#endif
  ASSERT_TRUE(f != nullptr);
  ASSERT_EQ((size_t)1, fread(buff, contents.size(), 1, f));

  // Read past the end of the file
  fgetc(f);
  ASSERT_TRUE(feof(f));
  fclose(f);

  // Verify the file contents
  ASSERT_TRUE(!memcmp(contents.c_str(), buff, contents.size()));
  delete[] buff;

// Delete the file
#ifdef _WIN32
  ASSERT_EQ(0, _unlink(fileName));
#else
  ASSERT_EQ(0, unlink(fileName));
#endif
}

bool g_ReadHandlerTesterDeleted = false;

class ReadHandlerTester : public CefReadHandler {
 public:
  ReadHandlerTester() = default;
  ~ReadHandlerTester() override { g_ReadHandlerTesterDeleted = true; }

  size_t Read(void* ptr, size_t size, size_t n) override {
    read_called_ = true;
    read_ptr_ = ptr;
    read_size_ = size;
    read_n_ = n;
    return 10;
  }

  int Seek(int64_t offset, int whence) override {
    seek_called_ = true;
    seek_offset_ = offset;
    seek_whence_ = whence;
    return 10;
  }

  int64_t Tell() override {
    tell_called_ = true;
    return 10;
  }

  int Eof() override {
    eof_called_ = true;
    return 10;
  }

  bool MayBlock() override { return false; }

  bool read_called_ = false;
  const void* read_ptr_ = nullptr;
  size_t read_size_ = 0;
  size_t read_n_ = 0;

  bool seek_called_ = false;
  int64_t seek_offset_ = 0;
  int seek_whence_ = 0;

  bool tell_called_ = false;

  bool eof_called_ = false;

  IMPLEMENT_REFCOUNTING(ReadHandlerTester);
};

TEST(StreamTest, ReadHandler) {
  ReadHandlerTester* handler = new ReadHandlerTester();
  ASSERT_TRUE(handler != nullptr);

  CefRefPtr<CefStreamReader> stream(CefStreamReader::CreateForHandler(handler));
  ASSERT_TRUE(stream.get() != nullptr);
  ASSERT_FALSE(stream->MayBlock());

  // CefReadHandler Read
  const char* read_ptr = "My data";
  size_t read_size = sizeof(read_ptr);
  size_t read_n = 1;
  size_t read_res = stream->Read(
      static_cast<void*>(const_cast<char*>(read_ptr)), read_size, read_n);
  ASSERT_TRUE(handler->read_called_);
  ASSERT_EQ((size_t)10, read_res);
  ASSERT_EQ(read_ptr, handler->read_ptr_);
  ASSERT_EQ(read_size, handler->read_size_);
  ASSERT_EQ(read_n, handler->read_n_);

  // CefReadHandler Seek
  int64_t seek_offset = 10;
  int seek_whence = SEEK_CUR;
  int seek_res = stream->Seek(seek_offset, seek_whence);
  ASSERT_TRUE(handler->seek_called_);
  ASSERT_EQ(10, seek_res);
  ASSERT_EQ(seek_offset, handler->seek_offset_);
  ASSERT_EQ(seek_whence, handler->seek_whence_);

  // CefReadHandler Tell
  int64_t tell_res = stream->Tell();
  ASSERT_TRUE(handler->tell_called_);
  ASSERT_EQ(10, tell_res);

  // CefReadHandler Eof
  int eof_res = stream->Eof();
  ASSERT_TRUE(handler->eof_called_);
  ASSERT_EQ(10, eof_res);

  // Delete the stream
  stream = nullptr;

  // Verify that the handler object was deleted
  ASSERT_TRUE(g_ReadHandlerTesterDeleted);
}

bool g_WriteHandlerTesterDeleted = false;

class WriteHandlerTester : public CefWriteHandler {
 public:
  WriteHandlerTester() = default;
  ~WriteHandlerTester() override { g_WriteHandlerTesterDeleted = true; }

  size_t Write(const void* ptr, size_t size, size_t n) override {
    write_called_ = true;
    write_ptr_ = ptr;
    write_size_ = size;
    write_n_ = n;
    return 10;
  }

  int Seek(int64_t offset, int whence) override {
    seek_called_ = true;
    seek_offset_ = offset;
    seek_whence_ = whence;
    return 10;
  }

  int64_t Tell() override {
    tell_called_ = true;
    return 10;
  }

  int Flush() override {
    flush_called_ = true;
    return 10;
  }

  bool MayBlock() override { return false; }

  bool write_called_ = false;
  const void* write_ptr_ = nullptr;
  size_t write_size_ = 0;
  size_t write_n_ = 0;

  bool seek_called_ = false;
  int64_t seek_offset_ = 0;
  int seek_whence_ = 0;

  bool tell_called_ = false;

  bool flush_called_ = false;

  IMPLEMENT_REFCOUNTING(WriteHandlerTester);
};

TEST(StreamTest, WriteHandler) {
  WriteHandlerTester* handler = new WriteHandlerTester();
  ASSERT_TRUE(handler != nullptr);

  CefRefPtr<CefStreamWriter> stream(CefStreamWriter::CreateForHandler(handler));
  ASSERT_TRUE(stream.get() != nullptr);
  ASSERT_FALSE(stream->MayBlock());

  // CefWriteHandler Write
  const char* write_ptr = "My data";
  size_t write_size = sizeof(write_ptr);
  size_t write_n = 1;
  size_t write_res = stream->Write(write_ptr, write_size, write_n);
  ASSERT_TRUE(handler->write_called_);
  ASSERT_EQ((size_t)10, write_res);
  ASSERT_EQ(write_ptr, handler->write_ptr_);
  ASSERT_EQ(write_size, handler->write_size_);
  ASSERT_EQ(write_n, handler->write_n_);

  // CefWriteHandler Seek
  int64_t seek_offset = 10;
  int seek_whence = SEEK_CUR;
  int seek_res = stream->Seek(seek_offset, seek_whence);
  ASSERT_TRUE(handler->seek_called_);
  ASSERT_EQ(10, seek_res);
  ASSERT_EQ(seek_offset, handler->seek_offset_);
  ASSERT_EQ(seek_whence, handler->seek_whence_);

  // CefWriteHandler Tell
  int64_t tell_res = stream->Tell();
  ASSERT_TRUE(handler->tell_called_);
  ASSERT_EQ(10, tell_res);

  // CefWriteHandler Flush
  int flush_res = stream->Flush();
  ASSERT_TRUE(handler->flush_called_);
  ASSERT_EQ(10, flush_res);

  // Delete the stream
  stream = nullptr;

  // Verify that the handler object was deleted
  ASSERT_TRUE(g_WriteHandlerTesterDeleted);
}
