// Copyright (c) 2013 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 <stddef.h>

#include <algorithm>
#include <set>
#include <sstream>

#include "base/command_line.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "tools/gn/commands.h"
#include "tools/gn/config.h"
#include "tools/gn/desc_builder.h"
#include "tools/gn/setup.h"
#include "tools/gn/standard_out.h"
#include "tools/gn/switches.h"
#include "tools/gn/target.h"
#include "tools/gn/variables.h"

namespace commands {

namespace {

// Desc-specific command line switches.
const char kBlame[] = "blame";
const char kTree[] = "tree";
const char kAll[] = "all";

// Prints value with specified indentation level
void PrintValue(const base::Value* value, int indentLevel) {
  std::string indent(indentLevel * 2, ' ');
  const base::ListValue* list_value = nullptr;
  const base::DictionaryValue* dict_value = nullptr;
  std::string string_value;
  bool bool_value = false;
  if (value->GetAsList(&list_value)) {
    for (const auto& v : *list_value) {
      PrintValue(v.get(), indentLevel);
    }
  } else if (value->GetAsString(&string_value)) {
    OutputString(indent);
    OutputString(string_value);
    OutputString("\n");
  } else if (value->GetAsBoolean(&bool_value)) {
    OutputString(indent);
    OutputString(bool_value ? "true" : "false");
    OutputString("\n");
  } else if (value->GetAsDictionary(&dict_value)) {
    base::DictionaryValue::Iterator iter(*dict_value);
    while (!iter.IsAtEnd()) {
      OutputString(indent + iter.key() + "\n");
      PrintValue(&iter.value(), indentLevel + 1);
      iter.Advance();
    }
  } else if (value->IsType(base::Value::TYPE_NULL)) {
    OutputString(indent + "<null>\n");
  }
}

// Default handler for property
void DefaultHandler(const std::string& name, const base::Value* value) {
  OutputString("\n");
  OutputString(name);
  OutputString("\n");
  PrintValue(value, 1);
}

// Specific handler for properties that need different treatment

// Prints label and property value on one line, capitalizing the label.
void LabelHandler(std::string name, const base::Value* value) {
  name[0] = base::ToUpperASCII(name[0]);
  std::string string_value;
  if (value->GetAsString(&string_value)) {
    OutputString(name + ": ", DECORATION_YELLOW);
    OutputString(string_value + "\n");
  }
}

void VisibilityHandler(const std::string& name, const base::Value* value) {
  const base::ListValue* list;
  if (value->GetAsList(&list)) {
    if (list->empty()) {
      base::StringValue str("(no visibility)");
      DefaultHandler(name, &str);
    } else {
      DefaultHandler(name, value);
    }
  }
}

void PublicHandler(const std::string& name, const base::Value* value) {
  std::string p;
  if (value->GetAsString(&p)) {
    if (p == "*") {
      base::StringValue str("[All headers listed in the sources are public.]");
      DefaultHandler(name, &str);
      return;
    }
  }
  DefaultHandler(name, value);
}

void ConfigsHandler(const std::string& name, const base::Value* value) {
  bool tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree);
  if (tree)
    DefaultHandler(name + " tree (in order applying)", value);
  else
    DefaultHandler(name + " (in order applying, try also --tree)", value);
}

void DepsHandler(const std::string& name, const base::Value* value) {
  bool tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree);
  bool all = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree);
  if (tree) {
    DefaultHandler("Dependency tree", value);
  } else {
    if (!all) {
      DefaultHandler(
          "Direct dependencies "
          "(try also \"--all\", \"--tree\", or even \"--all --tree\")",
          value);
    } else {
      DefaultHandler("All recursive dependencies", value);
    }
  }
}

// Outputs need special processing when output patterns are present.
void ProcessOutputs(base::DictionaryValue* target) {
  base::ListValue* patterns = nullptr;
  base::ListValue* outputs = nullptr;
  target->GetList("output_patterns", &patterns);
  target->GetList(variables::kOutputs, &outputs);

  if (outputs || patterns) {
    OutputString("\noutputs\n");
    int indent = 1;
    if (patterns) {
      OutputString("  Output patterns\n");
      indent = 2;
      PrintValue(patterns, indent);
      OutputString("\n  Resolved output file list\n");
    }
    if (outputs)
      PrintValue(outputs, indent);

    target->Remove("output_patterns", nullptr);
    target->Remove(variables::kOutputs, nullptr);
  }
}

bool PrintTarget(const Target* target,
                 const std::string& what,
                 bool single_target,
                 bool all,
                 bool tree,
                 bool blame) {
  std::unique_ptr<base::DictionaryValue> dict =
      DescBuilder::DescriptionForTarget(target, what, all, tree, blame);
  if (!what.empty() && dict->empty()) {
    OutputString("Don't know how to display \"" + what + "\" for \"" +
                 Target::GetStringForOutputType(target->output_type()) +
                 "\".\n");
    return false;
  }
  // Print single value, without any headers
  if (!what.empty() && dict->size() == 1 && single_target) {
    base::DictionaryValue::Iterator iter(*dict);
    PrintValue(&iter.value(), 0);
    return true;
  }

  OutputString("Target ", DECORATION_YELLOW);
  OutputString(target->label().GetUserVisibleName(false));
  OutputString("\n");

  std::unique_ptr<base::Value> v;
#define HANDLER(property, handler_name) \
  if (dict->Remove(property, &v)) {     \
    handler_name(property, v.get());    \
  }

  // Entries with DefaultHandler are present to enforce order
  HANDLER("type", LabelHandler);
  HANDLER("toolchain", LabelHandler);
  HANDLER(variables::kVisibility, VisibilityHandler);
  HANDLER(variables::kTestonly, DefaultHandler);
  HANDLER(variables::kCheckIncludes, DefaultHandler);
  HANDLER(variables::kAllowCircularIncludesFrom, DefaultHandler);
  HANDLER(variables::kSources, DefaultHandler);
  HANDLER(variables::kPublic, PublicHandler);
  HANDLER(variables::kInputs, DefaultHandler);
  HANDLER(variables::kConfigs, ConfigsHandler);
  HANDLER(variables::kPublicConfigs, ConfigsHandler);
  HANDLER(variables::kAllDependentConfigs, ConfigsHandler);
  HANDLER(variables::kScript, DefaultHandler);
  HANDLER(variables::kArgs, DefaultHandler);
  HANDLER(variables::kDepfile, DefaultHandler);
  ProcessOutputs(dict.get());
  HANDLER("bundle_data", DefaultHandler);
  HANDLER(variables::kArflags, DefaultHandler);
  HANDLER(variables::kAsmflags, DefaultHandler);
  HANDLER(variables::kCflags, DefaultHandler);
  HANDLER(variables::kCflagsC, DefaultHandler);
  HANDLER(variables::kCflagsCC, DefaultHandler);
  HANDLER(variables::kCflagsObjC, DefaultHandler);
  HANDLER(variables::kCflagsObjCC, DefaultHandler);
  HANDLER(variables::kDefines, DefaultHandler);
  HANDLER(variables::kIncludeDirs, DefaultHandler);
  HANDLER(variables::kLdflags, DefaultHandler);
  HANDLER(variables::kPrecompiledHeader, DefaultHandler);
  HANDLER(variables::kPrecompiledSource, DefaultHandler);
  HANDLER(variables::kDeps, DepsHandler);
  HANDLER(variables::kLibs, DefaultHandler);
  HANDLER(variables::kLibDirs, DefaultHandler);

#undef HANDLER

  // Process the rest (if any)
  base::DictionaryValue::Iterator iter(*dict);
  while (!iter.IsAtEnd()) {
    DefaultHandler(iter.key(), &iter.value());
    iter.Advance();
  }

  return true;
}

bool PrintConfig(const Config* config,
                 const std::string& what,
                 bool single_config) {
  std::unique_ptr<base::DictionaryValue> dict =
      DescBuilder::DescriptionForConfig(config, what);
  if (!what.empty() && dict->empty()) {
    OutputString("Don't know how to display \"" + what + "\" for a config.\n");
    return false;
  }
  // Print single value, without any headers
  if (!what.empty() && dict->size() == 1 && single_config) {
    base::DictionaryValue::Iterator iter(*dict);
    PrintValue(&iter.value(), 0);
    return true;
  }

  OutputString("Config: ", DECORATION_YELLOW);
  OutputString(config->label().GetUserVisibleName(false));
  OutputString("\n");

  std::unique_ptr<base::Value> v;
#define HANDLER(property, handler_name) \
  if (dict->Remove(property, &v)) {     \
    handler_name(property, v.get());    \
  }

  HANDLER("toolchain", LabelHandler);
  if (!config->configs().empty()) {
    OutputString(
        "(This is a composite config, the values below are after the\n"
        "expansion of the child configs.)\n");
  }
  HANDLER(variables::kArflags, DefaultHandler);
  HANDLER(variables::kAsmflags, DefaultHandler);
  HANDLER(variables::kCflags, DefaultHandler);
  HANDLER(variables::kCflagsC, DefaultHandler);
  HANDLER(variables::kCflagsCC, DefaultHandler);
  HANDLER(variables::kCflagsObjC, DefaultHandler);
  HANDLER(variables::kCflagsObjCC, DefaultHandler);
  HANDLER(variables::kDefines, DefaultHandler);
  HANDLER(variables::kIncludeDirs, DefaultHandler);
  HANDLER(variables::kLdflags, DefaultHandler);
  HANDLER(variables::kLibs, DefaultHandler);
  HANDLER(variables::kLibDirs, DefaultHandler);
  HANDLER(variables::kPrecompiledHeader, DefaultHandler);
  HANDLER(variables::kPrecompiledSource, DefaultHandler);

#undef HANDLER

  return true;
}

}  // namespace

// desc ------------------------------------------------------------------------

const char kDesc[] = "desc";
const char kDesc_HelpShort[] =
    "desc: Show lots of insightful information about a target or config.";
const char kDesc_Help[] =
    R"(gn desc <out_dir> <label or pattern> [<what to show>] [--blame] "
[--format=json]

  Displays information about a given target or config. The build build
  parameters will be taken for the build in the given <out_dir>.

  The <label or pattern> can be a target label, a config label, or a label
  pattern (see "gn help label_pattern"). A label pattern will only match
  targets.

Possibilities for <what to show>

  (If unspecified an overall summary will be displayed.)

  all_dependent_configs
  allow_circular_includes_from
  arflags [--blame]
  args
  cflags [--blame]
  cflags_cc [--blame]
  cflags_cxx [--blame]
  check_includes
  configs [--tree] (see below)
  defines [--blame]
  depfile
  deps [--all] [--tree] (see below)
  include_dirs [--blame]
  inputs
  ldflags [--blame]
  lib_dirs
  libs
  outputs
  public_configs
  public
  script
  sources
  testonly
  visibility

  runtime_deps
      Compute all runtime deps for the given target. This is a computed list
      and does not correspond to any GN variable, unlike most other values
      here.

      The output is a list of file names relative to the build directory. See
      "gn help runtime_deps" for how this is computed. This also works with
      "--blame" to see the source of the dependency.

Shared flags
)"

ALL_TOOLCHAINS_SWITCH_HELP

R"(
  --format=json
      Format the output as JSON instead of text.

Target flags

  --blame
      Used with any value specified on a config, this will name the config that
      cause that target to get the flag. This doesn't currently work for libs
      and lib_dirs because those are inherited and are more complicated to
      figure out the blame (patches welcome).

Configs

  The "configs" section will list all configs that apply. For targets this will
  include configs specified in the "configs" variable of the target, and also
  configs pushed onto this target via public or "all dependent" configs.

  Configs can have child configs. Specifying --tree will show the hierarchy.

Printing outputs

  The "outputs" section will list all outputs that apply, including the outputs
  computed from the tool definition (eg for "executable", "static_library", ...
  targets).

Printing deps

  Deps will include all public, private, and data deps (TODO this could be
  clarified and enhanced) sorted in order applying. The following may be used:

  --all
      Collects all recursive dependencies and prints a sorted flat list. Also
      usable with --tree (see below).
)"

    TARGET_PRINTING_MODE_COMMAND_LINE_HELP
"\n"
    TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP

R"(
  --tree
      Print a dependency tree. By default, duplicates will be elided with "..."
      but when --all and -tree are used together, no eliding will be performed.

      The "deps", "public_deps", and "data_deps" will all be included in the
      tree.

      Tree output can not be used with the filtering or output flags: --as,
      --type, --testonly.
)"

TARGET_TYPE_FILTER_COMMAND_LINE_HELP

R"(Note

  This command will show the full name of directories and source files, but
  when directories and source paths are written to the build file, they will be
  adjusted to be relative to the build directory. So the values for paths
  displayed by this command won't match (but should mean the same thing).

Examples

  gn desc out/Debug //base:base
      Summarizes the given target.

  gn desc out/Foo :base_unittests deps --tree
      Shows a dependency tree of the "base_unittests" project in
      the current directory.

  gn desc out/Debug //base defines --blame
      Shows defines set for the //base:base target, annotated by where
      each one was set from.
)";

int RunDesc(const std::vector<std::string>& args) {
  if (args.size() != 2 && args.size() != 3) {
    Err(Location(), "You're holding it wrong.",
        "Usage: \"gn desc <out_dir> <target_name> [<what to display>]\"")
        .PrintToStdout();
    return 1;
  }
  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();

  // Deliberately leaked to avoid expensive process teardown.
  Setup* setup = new Setup;
  if (!setup->DoSetup(args[0], false))
    return 1;
  if (!setup->Run())
    return 1;

  // Resolve target(s) and config from inputs.
  UniqueVector<const Target*> target_matches;
  UniqueVector<const Config*> config_matches;
  UniqueVector<const Toolchain*> toolchain_matches;
  UniqueVector<SourceFile> file_matches;

  std::vector<std::string> target_list;
  target_list.push_back(args[1]);

  if (!ResolveFromCommandLineInput(
          setup, target_list, cmdline->HasSwitch(switches::kAllToolchains),
          &target_matches, &config_matches, &toolchain_matches, &file_matches))
    return 1;

  std::string what_to_print;
  if (args.size() == 3)
    what_to_print = args[2];

  bool json = cmdline->GetSwitchValueASCII("format") == "json";

  if (json) {
    // Convert all targets/configs to JSON, serialize and print them
    auto res = base::MakeUnique<base::DictionaryValue>();
    if (!target_matches.empty()) {
      for (const auto* target : target_matches) {
        res->SetWithoutPathExpansion(
            target->label().GetUserVisibleName(
                target->settings()->default_toolchain_label()),
            DescBuilder::DescriptionForTarget(
                target, what_to_print, cmdline->HasSwitch(kAll),
                cmdline->HasSwitch(kTree), cmdline->HasSwitch(kBlame)));
      }
    } else if (!config_matches.empty()) {
      for (const auto* config : config_matches) {
        res->SetWithoutPathExpansion(
            config->label().GetUserVisibleName(false),
            DescBuilder::DescriptionForConfig(config, what_to_print));
      }
    }
    std::string s;
    base::JSONWriter::WriteWithOptions(
        *res.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &s);
    OutputString(s);
  } else {
    // Regular (non-json) formatted output
    bool multiple_outputs = (target_matches.size() + config_matches.size()) > 1;

    bool printed_output = false;
    for (const Target* target : target_matches) {
      if (printed_output)
        OutputString("\n\n");
      printed_output = true;

      if (!PrintTarget(target, what_to_print, !multiple_outputs,
                       cmdline->HasSwitch(kAll), cmdline->HasSwitch(kTree),
                       cmdline->HasSwitch(kBlame)))
        return 1;
    }
    for (const Config* config : config_matches) {
      if (printed_output)
        OutputString("\n\n");
      printed_output = true;

      if (!PrintConfig(config, what_to_print, !multiple_outputs))
        return 1;
    }
  }

  return 0;
}

}  // namespace commands
