// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "bin/file.h"
#include "bin/dartutils.h"
#include "bin/directory.h"
#include "bin/test_utils.h"
#include "platform/assert.h"
#include "platform/globals.h"
#include "vm/unit_test.h"

namespace dart {

TEST_CASE(Read) {
  const char* kFilename = bin::test::GetFileName("runtime/bin/file_test.cc");
  bin::File* file = bin::File::Open(nullptr, kFilename, bin::File::kRead);
  EXPECT(file != nullptr);
  char buffer[16];
  buffer[0] = '\0';
  EXPECT(file->ReadFully(buffer, 13));  // ReadFully returns true.
  buffer[13] = '\0';
  EXPECT_STREQ("// Copyright ", buffer);
  EXPECT(!file->WriteByte(1));  // Cannot write to a read-only file.
  file->Release();
}

TEST_CASE(OpenUri_RelativeFilename) {
  const char* kFilename = bin::test::GetFileName("runtime/bin/file_test.cc");
  char* encoded = reinterpret_cast<char*>(
      bin::DartUtils::ScopedCString(strlen(kFilename) * 3 + 1));
  char* t = encoded;
  // percent-encode all characters 'c'
  for (const char* p = kFilename; *p != '\0'; p++) {
    if (*p == 'c') {
      *t++ = '%';
      *t++ = '6';
      *t++ = '3';
    } else {
      *t++ = *p;
    }
  }
  *t = 0;
  bin::File* file = bin::File::OpenUri(nullptr, encoded, bin::File::kRead);
  EXPECT(file != nullptr);
  char buffer[16];
  buffer[0] = '\0';
  EXPECT(file->ReadFully(buffer, 13));  // ReadFully returns true.
  buffer[13] = '\0';
  EXPECT_STREQ("// Copyright ", buffer);
  EXPECT(!file->WriteByte(1));  // Cannot write to a read-only file.
  file->Release();
}

TEST_CASE(OpenUri_AbsoluteFilename) {
  const char* kRelativeFilename =
      bin::test::GetFileName("runtime/bin/file_test.cc");
  const char* kFilename =
      bin::File::GetCanonicalPath(nullptr, kRelativeFilename);
  EXPECT_NOTNULL(kFilename);
  char* encoded = reinterpret_cast<char*>(
      bin::DartUtils::ScopedCString(strlen(kFilename) * 3 + 1));
  char* t = encoded;
  // percent-encode all characters 'c'
  for (const char* p = kFilename; *p != '\0'; p++) {
    if (*p == 'c') {
      *t++ = '%';
      *t++ = '6';
      *t++ = '3';
    } else {
      *t++ = *p;
    }
  }
  *t = 0;
  bin::File* file = bin::File::OpenUri(nullptr, encoded, bin::File::kRead);
  EXPECT(file != nullptr);
  char buffer[16];
  buffer[0] = '\0';
  EXPECT(file->ReadFully(buffer, 13));  // ReadFully returns true.
  buffer[13] = '\0';
  EXPECT_STREQ("// Copyright ", buffer);
  EXPECT(!file->WriteByte(1));  // Cannot write to a read-only file.
  file->Release();
}

static const char* Concat(const char* a, const char* b) {
  const intptr_t len = strlen(a) + strlen(b);
  char* c = bin::DartUtils::ScopedCString(len + 1);
  EXPECT_NOTNULL(c);
  snprintf(c, len + 1, "%s%s", a, b);
  return c;
}

TEST_CASE(OpenUri_ValidUri) {
  const char* kRelativeFilename =
      bin::test::GetFileName("runtime/bin/file_test.cc");
  const char* kAbsoluteFilename =
      bin::File::GetCanonicalPath(nullptr, kRelativeFilename);
  EXPECT_NOTNULL(kAbsoluteFilename);
  const char* kFilename = Concat("file:///", kAbsoluteFilename);

  char* encoded = reinterpret_cast<char*>(
      bin::DartUtils::ScopedCString(strlen(kFilename) * 3 + 1));
  char* t = encoded;
  // percent-encode all characters 'c'
  for (const char* p = kFilename; *p != '\0'; p++) {
    if (*p == 'c') {
      *t++ = '%';
      *t++ = '6';
      *t++ = '3';
    } else {
      *t++ = *p;
    }
  }
  *t = 0;
  bin::File* file = bin::File::OpenUri(nullptr, encoded, bin::File::kRead);
  EXPECT(file != nullptr);
  char buffer[16];
  buffer[0] = '\0';
  EXPECT(file->ReadFully(buffer, 13));  // ReadFully returns true.
  buffer[13] = '\0';
  EXPECT_STREQ("// Copyright ", buffer);
  EXPECT(!file->WriteByte(1));  // Cannot write to a read-only file.
  file->Release();
}

TEST_CASE(OpenUri_UriWithSpaces) {
  const char* kRelativeFilename =
      bin::test::GetFileName("runtime/bin/file_test.cc");
  const char* strSystemTemp = bin::Directory::SystemTemp(nullptr);
  EXPECT_NOTNULL(strSystemTemp);
  const char* kTempDir = Concat(strSystemTemp, "/foo bar");
  const char* strTempDir = bin::Directory::CreateTemp(nullptr, kTempDir);
  EXPECT_NOTNULL(strTempDir);
  const char* kTargetFilename = Concat(strTempDir, "/file test.cc");
  bool result = bin::File::Copy(nullptr, kRelativeFilename, kTargetFilename);
  EXPECT(result);

  const char* kAbsoluteFilename =
      bin::File::GetCanonicalPath(nullptr, kTargetFilename);
  EXPECT_NOTNULL(kAbsoluteFilename);
  const char* kFilename = Concat("file:///", kAbsoluteFilename);

  char* encoded = reinterpret_cast<char*>(
      bin::DartUtils::ScopedCString(strlen(kFilename) * 3 + 1));
  char* t = encoded;
  // percent-encode all spaces
  for (const char* p = kFilename; *p != '\0'; p++) {
    if (*p == ' ') {
      *t++ = '%';
      *t++ = '2';
      *t++ = '0';
    } else {
      *t++ = *p;
    }
  }
  *t = 0;
  printf("encoded: %s\n", encoded);
  bin::File* file = bin::File::OpenUri(nullptr, encoded, bin::File::kRead);
  EXPECT(file != nullptr);
  char buffer[16];
  buffer[0] = '\0';
  EXPECT(file->ReadFully(buffer, 13));  // ReadFully returns true.
  buffer[13] = '\0';
  EXPECT_STREQ("// Copyright ", buffer);
  EXPECT(!file->WriteByte(1));  // Cannot write to a read-only file.
  file->Release();
  bin::Directory::Delete(nullptr, strTempDir, /* recursive= */ true);
}

TEST_CASE(OpenUri_InvalidUriPercentEncoding) {
  const char* kFilename = bin::test::GetFileName("runtime/bin/file_test.cc");
  char* encoded = reinterpret_cast<char*>(
      bin::DartUtils::ScopedCString(strlen(kFilename) * 3 + 1));
  char* t = encoded;
  // percent-encode all characters 'c'
  for (const char* p = kFilename; *p != '\0'; p++) {
    if (*p == 'c') {
      *t++ = '%';
      *t++ = 'f';
      *t++ = 'o';
    } else {
      *t++ = *p;
    }
  }
  *t = 0;
  bin::File* file = bin::File::OpenUri(nullptr, encoded, bin::File::kRead);
  EXPECT(file == nullptr);
}

TEST_CASE(OpenUri_TruncatedUriPercentEncoding) {
  const char* kFilename = bin::test::GetFileName("runtime/bin/file_test.cc");
  char* encoded = reinterpret_cast<char*>(
      bin::DartUtils::ScopedCString(strlen(kFilename) * 3 + 1));
  char* t = encoded;
  // percent-encode all characters 'c'
  for (const char* p = kFilename; *p != '\0'; p++) {
    if (*p == 'c') {
      *t++ = '%';
      *t++ = 'f';
      *t++ = 'o';
    } else {
      *t++ = *p;
    }
  }
  *(t - 1) = 0;  // truncate last uri encoding
  bin::File* file = bin::File::OpenUri(nullptr, encoded, bin::File::kRead);
  EXPECT(file == nullptr);
}

TEST_CASE(FileLength) {
  const char* kFilename =
      bin::test::GetFileName("runtime/tests/vm/data/fixed_length_file");
  bin::File* file = bin::File::Open(nullptr, kFilename, bin::File::kRead);
  EXPECT(file != nullptr);
  EXPECT_EQ(42, file->Length());
  file->Release();
}

TEST_CASE(FilePosition) {
  char buf[42];
  const char* kFilename =
      bin::test::GetFileName("runtime/tests/vm/data/fixed_length_file");
  bin::File* file = bin::File::Open(nullptr, kFilename, bin::File::kRead);
  EXPECT(file != nullptr);
  EXPECT(file->ReadFully(buf, 12));
  EXPECT_EQ(12, file->Position());
  EXPECT(file->ReadFully(buf, 6));
  EXPECT_EQ(18, file->Position());
  file->Release();
}

}  // namespace dart
