// Copyright 2018 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 "gn/output_conversion.h"

#include <sstream>

#include "gn/err.h"
#include "gn/input_conversion.h"
#include "gn/scope.h"
#include "gn/template.h"
#include "gn/test_with_scheduler.h"
#include "gn/test_with_scope.h"
#include "gn/value.h"
#include "util/test/test.h"

namespace {

// InputConversion needs a global scheduler object.
class OutputConversionTest : public TestWithScheduler {
 public:
  OutputConversionTest() = default;

  const Settings* settings() { return setup_.settings(); }
  Scope* scope() { return setup_.scope(); }

 private:
  TestWithScope setup_;
};

}  // namespace

TEST_F(OutputConversionTest, ListLines) {
  Err err;
  Value output(nullptr, Value::LIST);
  output.list_value().push_back(Value(nullptr, ""));
  output.list_value().push_back(Value(nullptr, "foo"));
  output.list_value().push_back(Value(nullptr, ""));
  output.list_value().push_back(Value(nullptr, "bar"));
  std::ostringstream result;
  ConvertValueToOutput(settings(), output, Value(nullptr, "list lines"), result,
                       &err);

  EXPECT_FALSE(err.has_error());
  EXPECT_EQ("\nfoo\n\nbar\n", result.str());
}

TEST_F(OutputConversionTest, String) {
  Err err;
  Value output(nullptr, "foo bar");
  std::ostringstream result;
  ConvertValueToOutput(settings(), output, Value(nullptr, "string"), result,
                       &err);

  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), "foo bar");
}

TEST_F(OutputConversionTest, StringInt) {
  Err err;
  Value output(nullptr, int64_t(6));
  std::ostringstream result;
  ConvertValueToOutput(settings(), output, Value(nullptr, "string"), result,
                       &err);

  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), "\"6\"");
}

TEST_F(OutputConversionTest, StringBool) {
  Err err;
  Value output(nullptr, true);
  std::ostringstream result;
  ConvertValueToOutput(settings(), output, Value(nullptr, "string"), result,
                       &err);

  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), "\"true\"");
}

TEST_F(OutputConversionTest, StringList) {
  Err err;
  Value output(nullptr, Value::LIST);
  output.list_value().push_back(Value(nullptr, "foo"));
  output.list_value().push_back(Value(nullptr, "bar"));
  output.list_value().push_back(Value(nullptr, int64_t(6)));
  std::ostringstream result;
  ConvertValueToOutput(settings(), output, Value(nullptr, "string"), result,
                       &err);

  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), "\"[\"foo\", \"bar\", 6]\"");
}

TEST_F(OutputConversionTest, StringScope) {
  Err err;

  auto new_scope = std::make_unique<Scope>(settings());
  // Add some values to the scope.
  Value value(nullptr, "hello");
  new_scope->SetValue("v", value, nullptr);
  std::string_view private_var_name("_private");
  new_scope->SetValue(private_var_name, value, nullptr);

  std::ostringstream result;
  ConvertValueToOutput(settings(), Value(nullptr, std::move(new_scope)),
                       Value(nullptr, "string"), result, &err);
  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), "\"{\n  _private = \"hello\"\n  v = \"hello\"\n}\"");
}

TEST_F(OutputConversionTest, ValueString) {
  Err err;
  Value output(nullptr, "foo bar");
  std::ostringstream result;
  ConvertValueToOutput(settings(), output, Value(nullptr, "value"), result,
                       &err);

  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), "\"foo bar\"");
}

TEST_F(OutputConversionTest, ValueInt) {
  Err err;
  Value output(nullptr, int64_t(6));
  std::ostringstream result;
  ConvertValueToOutput(settings(), output, Value(nullptr, "value"), result,
                       &err);

  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), "6");
}

TEST_F(OutputConversionTest, ValueBool) {
  Err err;
  Value output(nullptr, true);
  std::ostringstream result;
  ConvertValueToOutput(settings(), output, Value(nullptr, "value"), result,
                       &err);

  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), "true");
}

TEST_F(OutputConversionTest, ValueList) {
  Err err;
  Value output(nullptr, Value::LIST);
  output.list_value().push_back(Value(nullptr, "foo"));
  output.list_value().push_back(Value(nullptr, "bar"));
  output.list_value().push_back(Value(nullptr, int64_t(6)));
  std::ostringstream result;
  ConvertValueToOutput(settings(), output, Value(nullptr, "value"), result,
                       &err);

  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), "[\"foo\", \"bar\", 6]");
}

TEST_F(OutputConversionTest, ValueScope) {
  Err err;

  auto new_scope = std::make_unique<Scope>(settings());
  // Add some values to the scope.
  Value value(nullptr, "hello");
  new_scope->SetValue("v", value, nullptr);
  std::string_view private_var_name("_private");
  new_scope->SetValue(private_var_name, value, nullptr);

  std::ostringstream result;
  ConvertValueToOutput(settings(), Value(nullptr, std::move(new_scope)),
                       Value(nullptr, "value"), result, &err);
  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), "{\n  _private = \"hello\"\n  v = \"hello\"\n}");
}

TEST_F(OutputConversionTest, JSON) {
  Err err;
  auto new_scope = std::make_unique<Scope>(settings());
  // Add some values to the scope.
  Value a_value(nullptr, "foo");
  new_scope->SetValue("a", a_value, nullptr);
  Value b_value(nullptr, int64_t(6));
  new_scope->SetValue("b", b_value, nullptr);

  auto c_scope = std::make_unique<Scope>(settings());
  Value e_value(nullptr, Value::LIST);
  e_value.list_value().push_back(Value(nullptr, "bar"));

  auto e_value_scope = std::make_unique<Scope>(settings());
  Value f_value(nullptr, "baz");
  e_value_scope->SetValue("f", f_value, nullptr);
  e_value.list_value().push_back(Value(nullptr, std::move(e_value_scope)));

  c_scope->SetValue("e", e_value, nullptr);

  new_scope->SetValue("c", Value(nullptr, std::move(c_scope)), nullptr);

  std::string expected(R"*({
  "a": "foo",
  "b": 6,
  "c": {
    "e": [
      "bar",
      {
        "f": "baz"
      }
    ]
  }
})*");
  std::ostringstream result;
  ConvertValueToOutput(settings(), Value(nullptr, std::move(new_scope)),
                       Value(nullptr, "json"), result, &err);
  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), expected);
}

TEST_F(OutputConversionTest, ValueEmpty) {
  Err err;
  std::ostringstream result;
  ConvertValueToOutput(settings(), Value(), Value(nullptr, ""), result, &err);
  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), "<void>");
}

TEST_F(OutputConversionTest, DefaultValue) {
  Err err;
  Value output(nullptr, "foo bar");
  std::ostringstream result;
  ConvertValueToOutput(settings(), output, Value(nullptr, ""), result, &err);

  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(result.str(), "foo bar");
}

TEST_F(OutputConversionTest, DefaultListLines) {
  Err err;
  Value output(nullptr, Value::LIST);
  output.list_value().push_back(Value(nullptr, ""));
  output.list_value().push_back(Value(nullptr, "foo"));
  output.list_value().push_back(Value(nullptr, ""));
  output.list_value().push_back(Value(nullptr, "bar"));
  std::ostringstream result;
  ConvertValueToOutput(settings(), output, Value(nullptr, ""), result, &err);

  EXPECT_FALSE(err.has_error());
  EXPECT_EQ("\nfoo\n\nbar\n", result.str());
}

TEST_F(OutputConversionTest, ReverseString) {
  Err err;
  std::string input("foo bar");
  Value result = ConvertInputToValue(settings(), input, nullptr,
                                     Value(nullptr, "string"), &err);
  EXPECT_FALSE(err.has_error());

  std::ostringstream reverse;
  ConvertValueToOutput(settings(), result, Value(nullptr, "string"), reverse,
                       &err);
  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(reverse.str(), input)
      << "actual: " << reverse.str() << "expected:" << input;
}

TEST_F(OutputConversionTest, ReverseListLines) {
  Err err;
  std::string input("\nfoo\nbar\n\n");
  Value result = ConvertInputToValue(settings(), input, nullptr,
                                     Value(nullptr, "list lines"), &err);
  EXPECT_FALSE(err.has_error());

  std::ostringstream reverse;
  ConvertValueToOutput(settings(), result, Value(nullptr, "list lines"),
                       reverse, &err);
  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(reverse.str(), input)
      << "actual: " << reverse.str() << "expected:" << input;
}

TEST_F(OutputConversionTest, ReverseValueString) {
  Err err;
  std::string input("\"str\"");
  Value result = ConvertInputToValue(settings(), input, nullptr,
                                     Value(nullptr, "value"), &err);
  EXPECT_FALSE(err.has_error());

  std::ostringstream reverse;
  ConvertValueToOutput(settings(), result, Value(nullptr, "value"), reverse,
                       &err);
  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(reverse.str(), input)
      << "actual: " << reverse.str() << "expected:" << input;
}

TEST_F(OutputConversionTest, ReverseValueInt) {
  Err err;
  std::string input("6");
  Value result = ConvertInputToValue(settings(), input, nullptr,
                                     Value(nullptr, "value"), &err);
  EXPECT_FALSE(err.has_error());

  std::ostringstream reverse;
  ConvertValueToOutput(settings(), result, Value(nullptr, "value"), reverse,
                       &err);
  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(reverse.str(), input)
      << "actual: " << reverse.str() << "expected:" << input;
}

TEST_F(OutputConversionTest, ReverseValueList) {
  Err err;
  std::string input("[\"a\", 5]");
  Value result = ConvertInputToValue(settings(), input, nullptr,
                                     Value(nullptr, "value"), &err);
  EXPECT_FALSE(err.has_error());

  std::ostringstream reverse;
  ConvertValueToOutput(settings(), result, Value(nullptr, "value"), reverse,
                       &err);
  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(reverse.str(), input)
      << "actual: " << reverse.str() << "expected:" << input;
}

TEST_F(OutputConversionTest, ReverseValueDict) {
  Err err;
  std::string input("  a = 5\n  b = \"foo\"\n  c = 7\n");
  Value result = ConvertInputToValue(settings(), input, nullptr,
                                     Value(nullptr, "scope"), &err);
  EXPECT_FALSE(err.has_error());

  std::ostringstream reverse;
  ConvertValueToOutput(settings(), result, Value(nullptr, "scope"), reverse,
                       &err);
  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(reverse.str(), input)
      << "actual: " << reverse.str() << "expected:" << input;
}

TEST_F(OutputConversionTest, ReverseValueEmpty) {
  Err err;
  Value result = ConvertInputToValue(settings(), "", nullptr,
                                     Value(nullptr, "value"), &err);
  EXPECT_FALSE(err.has_error());

  std::ostringstream reverse;
  ConvertValueToOutput(settings(), result, Value(nullptr, "value"), reverse,
                       &err);
  EXPECT_FALSE(err.has_error());
  EXPECT_EQ(reverse.str(), "");
}
