// 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 "gn/escape.h"
#include "gn/settings.h"
#include "gn/value.h"

namespace {

void ToString(const Value& output, std::ostream& out) {
  out << output.ToString(false);
}

void ToStringQuoted(const Value& output, std::ostream& out) {
  out << "\"" << output.ToString(false) << "\"";
}

void Indent(int indent, std::ostream& out) {
  for (int i = 0; i < indent; ++i)
    out << "  ";
}

// Forward declare so it can be used recursively.
void RenderScopeToJSON(const Value& output, std::ostream& out, int indent);

void RenderListToJSON(const Value& output, std::ostream& out, int indent) {
  assert(indent > 0);
  bool first = true;
  out << "[\n";
  for (const auto& value : output.list_value()) {
    if (!first)
      out << ",\n";
    Indent(indent, out);
    if (value.type() == Value::SCOPE)
      RenderScopeToJSON(value, out, indent + 1);
    else if (value.type() == Value::LIST)
      RenderListToJSON(value, out, indent + 1);
    else if (value.type() == Value::STRING)
      EscapeJSONStringToStream(out, value.ToString(false), EscapeOptions());
    else
      out << value.ToString(false);
    first = false;
  }
  out << "\n";
  Indent(indent - 1, out);
  out << "]";
}

void RenderScopeToJSON(const Value& output, std::ostream& out, int indent) {
  assert(indent > 0);
  Scope::KeyValueMap scope_values;
  output.scope_value()->GetCurrentScopeValues(&scope_values);
  bool first = true;
  out << "{\n";
  for (const auto& pair : scope_values) {
    if (!first)
      out << ",\n";
    Indent(indent, out);
    out << "\"" << pair.first << "\": ";
    if (pair.second.type() == Value::SCOPE)
      RenderScopeToJSON(pair.second, out, indent + 1);
    else if (pair.second.type() == Value::LIST)
      RenderListToJSON(pair.second, out, indent + 1);
    else if (pair.second.type() == Value::STRING)
      EscapeJSONStringToStream(out, pair.second.ToString(false),
                               EscapeOptions());
    else
      out << pair.second.ToString(false);
    first = false;
  }
  out << "\n";
  Indent(indent - 1, out);
  out << "}";
}

void OutputListLines(const Value& output, std::ostream& out) {
  assert(output.type() == Value::LIST);
  const std::vector<Value>& list = output.list_value();
  for (const auto& cur : list)
    out << cur.ToString(false) << "\n";
}

void OutputString(const Value& output, std::ostream& out) {
  if (output.type() == Value::NONE)
    return;
  if (output.type() == Value::STRING) {
    ToString(output, out);
    return;
  }
  ToStringQuoted(output, out);
}

void OutputValue(const Value& output, std::ostream& out) {
  if (output.type() == Value::NONE)
    return;
  if (output.type() == Value::STRING) {
    ToStringQuoted(output, out);
    return;
  }
  ToString(output, out);
}

// The direct Value::ToString call wraps the scope in '{}', which we don't want
// here for the top-level scope being output.
void OutputScope(const Value& output, std::ostream& out) {
  Scope::KeyValueMap scope_values;
  output.scope_value()->GetCurrentScopeValues(&scope_values);
  for (const auto& pair : scope_values) {
    out << "  " << pair.first << " = " << pair.second.ToString(true) << "\n";
  }
}

void OutputDefault(const Value& output, std::ostream& out) {
  if (output.type() == Value::LIST)
    OutputListLines(output, out);
  else
    ToString(output, out);
}

void OutputJSON(const Value& output, std::ostream& out) {
  if (output.type() == Value::SCOPE) {
    RenderScopeToJSON(output, out, /*indent=*/1);
    return;
  }
  if (output.type() == Value::LIST) {
    RenderListToJSON(output, out, /*indent=*/1);
    return;
  }
  ToStringQuoted(output, out);
}

void DoConvertValueToOutput(const Value& output,
                            const std::string& output_conversion,
                            const Value& original_output_conversion,
                            std::ostream& out,
                            Err* err) {
  if (output_conversion == "") {
    OutputDefault(output, out);
  } else if (output_conversion == "list lines") {
    if (output.type() != Value::LIST) {
      *err = Err(original_output_conversion, "Not a valid list.");
      return;
    }
    OutputListLines(output, out);
  } else if (output_conversion == "string") {
    OutputString(output, out);
  } else if (output_conversion == "value") {
    OutputValue(output, out);
  } else if (output_conversion == "json") {
    OutputJSON(output, out);
  } else if (output_conversion == "scope") {
    if (output.type() != Value::SCOPE) {
      *err = Err(original_output_conversion, "Not a valid scope.");
      return;
    }
    OutputScope(output, out);
  } else {
    // If we make it here, we didn't match any of the valid options.
    *err = Err(original_output_conversion, "Not a valid output_conversion.",
               "Run `gn help io_conversion` to see your options.");
  }
}

}  // namespace

void ConvertValueToOutput(const Settings* settings,
                          const Value& output,
                          const Value& output_conversion,
                          std::ostream& out,
                          Err* err) {
  if (output_conversion.type() == Value::NONE) {
    OutputDefault(output, out);
    return;
  }
  if (!output_conversion.VerifyTypeIs(Value::STRING, err))
    return;

  DoConvertValueToOutput(output, output_conversion.string_value(),
                         output_conversion, out, err);
}
