// 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 <algorithm>
#include <iterator>
#include <set>
#include <vector>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "gn/analyzer.h"
#include "gn/commands.h"
#include "gn/filesystem_utils.h"
#include "gn/location.h"
#include "gn/setup.h"
#include "gn/standard_out.h"
#include "gn/string_utils.h"

namespace commands {

const char kAnalyze[] = "analyze";
const char kAnalyze_HelpShort[] =
    "analyze: Analyze which targets are affected by a list of files.";
const char kAnalyze_Help[] =
    R"*(gn analyze <out_dir> <input_path> <output_path>

  Analyze which targets are affected by a list of files.

  This command takes three arguments:

  out_dir is the path to the build directory.

  input_path is a path to a file containing a JSON object with three fields:

   - "files": A list of the filenames to check.

   - "test_targets": A list of the labels for targets that are needed to run
     the tests we wish to run.

   - "additional_compile_targets" (optional): A list of the labels for targets
     that we wish to rebuild, but aren't necessarily needed for testing. The
     important difference between this field and "test_targets" is that if an
     item in the additional_compile_targets list refers to a group, then any
     dependencies of that group will be returned if they are out of date, but
     the group itself does not need to be. If the dependencies themselves are
     groups, the same filtering is repeated. This filtering can be used to
     avoid rebuilding dependencies of a group that are unaffected by the input
     files. The list may also contain the string "all" to refer to a
     pseudo-group that contains every root target in the build graph.

     This filtering behavior is also known as "pruning" the list of compile
     targets.

     If "additional_compile_targets" is absent, it defaults to the empty list.

  If input_path is -, input is read from stdin.

  output_path is a path indicating where the results of the command are to be
  written. The results will be a file containing a JSON object with one or more
  of following fields:

   - "compile_targets": A list of the labels derived from the input
     compile_targets list that are affected by the input files. Due to the way
     the filtering works for compile targets as described above, this list may
     contain targets that do not appear in the input list.

   - "test_targets": A list of the labels from the input test_targets list that
     are affected by the input files. This list will be a proper subset of the
     input list.

   - "invalid_targets": A list of any names from the input that do not exist in
     the build graph. If this list is non-empty, the "error" field will also be
     set to "Invalid targets".

   - "status": A string containing one of three values:

       - "Found dependency"
       - "No dependency"
       - "Found dependency (all)"

     In the first case, the lists returned in compile_targets and test_targets
     should be passed to ninja to build. In the second case, nothing was
     affected and no build is necessary. In the third case, GN could not
     determine the correct answer and returned the input as the output in order
     to be safe.

   - "error": This will only be present if an error occurred, and will contain
     a string describing the error. This includes cases where the input file is
     not in the right format, or contains invalid targets.

  If output_path is -, output is written to stdout.

  The command returns 1 if it is unable to read the input file or write the
  output file, or if there is something wrong with the build such that gen
  would also fail, and 0 otherwise. In particular, it returns 0 even if the
  "error" key is non-empty and a non-fatal error occurred. In other words, it
  tries really hard to always write something to the output JSON and convey
  errors that way rather than via return codes.
)*";

int RunAnalyze(const std::vector<std::string>& args) {
  if (args.size() != 3) {
    Err(Location(), "Unknown command format. See \"gn help analyze\"",
        "Usage: \"gn analyze <out_dir> <input_path> <output_path>")
        .PrintToStdout();
    return 1;
  }

  std::string input;
  if (args[1] == "-") {
    input = ReadStdin();
  } else {
    bool ret = base::ReadFileToString(UTF8ToFilePath(args[1]), &input);
    if (!ret) {
      Err(Location(), "Input file " + args[1] + " not found.").PrintToStdout();
      return 1;
    }
  }

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

  Err err;
  Analyzer analyzer(
      setup->builder(), setup->build_settings().build_config_file(),
      setup->GetDotFile(),
      setup->build_settings().build_args().build_args_dependency_files());
  std::string output = analyzer.Analyze(input, &err);
  if (err.has_error()) {
    err.PrintToStdout();
    return 1;
  }

  if (args[2] == "-") {
    OutputString(output + "\n");
  } else {
    WriteFile(UTF8ToFilePath(args[2]), output, &err);
    if (err.has_error()) {
      err.PrintToStdout();
      return 1;
    }
  }

  return 0;
}

}  // namespace commands
