// Copyright 2017 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <windows.h>

#include <algorithm>
#include <memory>
#include <string>

#include "src/main/cpp/blaze_util.h"
#include "src/main/cpp/blaze_util_platform.h"
#include "src/main/cpp/util/file.h"
#include "src/main/cpp/util/strings.h"
#include "googletest/include/gtest/gtest.h"

namespace blaze {

using std::string;
using std::unique_ptr;
using std::wstring;

// Asserts that the envvar named `key` is unset.
// Exercises GetEnvironmentVariable{A,W}, both with `key` and its lower case
// version, to make sure that envvar retrieval is case-insensitive (envvar names
// are case-insensitive on Windows).
//
// This is a macro so the assertions will have the correct line number.
#define ASSERT_ENVVAR_UNSET(/* const char* */ key)                            \
  {                                                                           \
    ASSERT_EQ(::GetEnvironmentVariableA(key, NULL, 0), (DWORD)0);             \
    ASSERT_EQ(                                                                \
        ::GetEnvironmentVariableA(blaze_util::AsLower(key).c_str(), NULL, 0), \
        (DWORD)0);                                                            \
    ASSERT_EQ(::GetEnvironmentVariableW(                                      \
                  blaze_util::CstringToWstring(key).get(), NULL, 0),          \
              (DWORD)0);                                                      \
    ASSERT_EQ(::GetEnvironmentVariableW(blaze_util::CstringToWstring(         \
                                            blaze_util::AsLower(key).c_str()) \
                                            .get(),                           \
                                        NULL, 0),                             \
              (DWORD)0);                                                      \
  }

// Asserts that the envvar named `key` is set to the `expected` value.
// Exercises GetEnvironmentVariable{A,W}, both with `key` and its lower case
// version, to make sure that envvar retrieval is case-insensitive (envvar names
// are case-insensitive on Windows).
//
// This is a macro so the assertions will have the correct line number.
#define ASSERT_ENVVAR(/* const (char* | string&) */ _key,                    \
                      /* const (char* | string&) */ _expected)               \
  {                                                                          \
    string key(_key);                                                        \
    string expected(_expected);                                              \
    DWORD size = ::GetEnvironmentVariableA(key.c_str(), NULL, 0);            \
    ASSERT_GT(size, (DWORD)0);                                               \
    unique_ptr<char[]> buf(new char[size]);                                  \
                                                                             \
    /* Assert that GetEnvironmentVariableA can retrieve the value. */        \
    ASSERT_EQ(::GetEnvironmentVariableA(key.c_str(), buf.get(), size),       \
              size - 1);                                                     \
    ASSERT_EQ(string(buf.get()), expected);                                  \
                                                                             \
    /* Assert that envvar keys are case-insensitive. */                      \
    string lkey(blaze_util::AsLower(key));                                   \
    ASSERT_EQ(::GetEnvironmentVariableA(lkey.c_str(), buf.get(), size),      \
              size - 1);                                                     \
    ASSERT_EQ(string(buf.get()), expected);                                  \
                                                                             \
    /* Assert that GetEnvironmentVariableW can retrieve the value. */        \
    wstring wkey(blaze_util::CstringToWstring(key.c_str()).get());           \
    wstring wexpected(blaze_util::CstringToWstring(expected.c_str()).get()); \
    size = ::GetEnvironmentVariableW(wkey.c_str(), NULL, 0);                 \
    ASSERT_GT(size, (DWORD)0);                                               \
    unique_ptr<WCHAR[]> wbuf(new WCHAR[size]);                               \
    ASSERT_EQ(::GetEnvironmentVariableW(wkey.c_str(), wbuf.get(), size),     \
              size - 1);                                                     \
    ASSERT_EQ(wstring(wbuf.get()), wexpected);                               \
                                                                             \
    /* Assert that widechar envvar keys are case-insensitive. */             \
    wstring wlkey(blaze_util::CstringToWstring(lkey.c_str()).get());         \
    ASSERT_EQ(::GetEnvironmentVariableW(wlkey.c_str(), wbuf.get(), size),    \
              size - 1);                                                     \
    ASSERT_EQ(wstring(wbuf.get()), wexpected);                               \
  }

TEST(BlazeUtilWindowsTest, TestGetEnv) {
  ASSERT_ENVVAR_UNSET("DOES_not_EXIST");

  string actual(GetEnv("TEST_SRCDIR"));
  ASSERT_NE(actual, "");

  std::replace(actual.begin(), actual.end(), '/', '\\');
  ASSERT_NE(actual.find(":\\"), string::npos);

  ASSERT_ENVVAR_UNSET("Bazel_TEST_Key1");
  ASSERT_TRUE(::SetEnvironmentVariableA("Bazel_TEST_Key1", "some_VALUE"));
  ASSERT_ENVVAR("Bazel_TEST_Key1", "some_VALUE");
  ASSERT_TRUE(::SetEnvironmentVariableA("Bazel_TEST_Key1", NULL));

  string long_string(MAX_PATH, 'a');
  string long_key = string("Bazel_TEST_Key2_") + long_string;
  string long_value = string("Bazel_TEST_Value2_") + long_string;

  ASSERT_ENVVAR_UNSET(long_key.c_str());
  ASSERT_TRUE(::SetEnvironmentVariableA(long_key.c_str(), long_value.c_str()));
  ASSERT_ENVVAR(long_key, long_value);
  ASSERT_TRUE(::SetEnvironmentVariableA(long_key.c_str(), NULL));
}

TEST(BlazeUtilWindowsTest, TestSetEnv) {
  ASSERT_ENVVAR_UNSET("Bazel_TEST_Key1");
  SetEnv("Bazel_TEST_Key1", "some_VALUE");
  ASSERT_ENVVAR("Bazel_TEST_Key1", "some_VALUE");
  SetEnv("Bazel_TEST_Key1", "");
  ASSERT_ENVVAR_UNSET("Bazel_TEST_Key1");

  string long_string(MAX_PATH, 'a');
  string long_key = string("Bazel_TEST_Key2_") + long_string;
  string long_value = string("Bazel_TEST_Value2_") + long_string;

  ASSERT_ENVVAR_UNSET(long_key.c_str());
  SetEnv(long_key, long_value);
  ASSERT_ENVVAR(long_key.c_str(), long_value.c_str());
  SetEnv(long_key, "");
  ASSERT_ENVVAR_UNSET(long_key.c_str());
}

TEST(BlazeUtilWindowsTest, TestUnsetEnv) {
  ASSERT_ENVVAR_UNSET("Bazel_TEST_Key1");
  SetEnv("Bazel_TEST_Key1", "some_VALUE");
  ASSERT_ENVVAR("Bazel_TEST_Key1", "some_VALUE");
  UnsetEnv("Bazel_TEST_Key1");
  ASSERT_ENVVAR_UNSET("Bazel_TEST_Key1");

  string long_string(MAX_PATH, 'a');
  string long_key = string("Bazel_TEST_Key2_") + long_string;
  string long_value = string("Bazel_TEST_Value2_") + long_string;

  ASSERT_ENVVAR_UNSET(long_key.c_str());
  SetEnv(long_key, long_value);
  ASSERT_ENVVAR(long_key.c_str(), long_value.c_str());
  UnsetEnv(long_key);
  ASSERT_ENVVAR_UNSET(long_key.c_str());
}

TEST(BlazeUtilWindowsTest, ConvertPathTest) {
  EXPECT_EQ("c:\\foo", ConvertPath("C:\\FOO"));
  EXPECT_EQ("c:\\", ConvertPath("c:/"));
  EXPECT_EQ("c:\\foo\\bar", ConvertPath("c:/../foo\\BAR\\.\\"));
  EXPECT_EQ("nul", MakeAbsolute("NUL"));
  EXPECT_EQ("nul", MakeAbsolute("nul"));
  EXPECT_EQ("nul", MakeAbsolute("/dev/null"));
}

TEST(BlazeUtilWindowsTest, TestMakeAbsolute) {
  EXPECT_EQ("c:\\foo\\bar", MakeAbsolute("C:\\foo\\BAR"));
  EXPECT_EQ("c:\\foo\\bar", MakeAbsolute("C:/foo/bar"));
  EXPECT_EQ("c:\\foo\\bar", MakeAbsolute("C:\\foo\\bar\\"));
  EXPECT_EQ("c:\\foo\\bar", MakeAbsolute("C:/foo/bar/"));
  EXPECT_EQ(blaze_util::AsLower(blaze_util::GetCwd()) + "\\foo",
            MakeAbsolute("foo"));
  EXPECT_EQ("nul", MakeAbsolute("NUL"));
  EXPECT_EQ("nul", MakeAbsolute("Nul"));
  EXPECT_EQ("nul", MakeAbsolute("nul"));
  EXPECT_EQ(blaze_util::AsLower(blaze_util::GetCwd()), MakeAbsolute(""));
  EXPECT_EQ("nul", MakeAbsolute("/dev/null"));
}

}  // namespace blaze
