// Copyright 2015 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 "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "gn/commands.h"
#include "gn/err.h"
#include "gn/filesystem_utils.h"
#include "gn/ninja_build_writer.h"
#include "gn/setup.h"

namespace {

bool CleanOneDir(const std::string& dir) {
  // Deliberately leaked to avoid expensive process teardown.
  Setup* setup = new Setup;
  if (!setup->DoSetup(dir, false))
    return false;

  base::FilePath build_dir(setup->build_settings().GetFullPath(
      SourceDir(setup->build_settings().build_dir().value())));

  // NOTE: Not all GN builds have args.gn file hence we also check here
  // if a build.ninja.d files exists instead.
  base::FilePath args_gn_file = build_dir.AppendASCII("args.gn");
  base::FilePath build_ninja_d_file = build_dir.AppendASCII("build.ninja.d");
  if (!base::PathExists(args_gn_file) &&
      !base::PathExists(build_ninja_d_file)) {
    Err(Location(),
        base::StringPrintf(
            "%s does not look like a build directory.\n",
            FilePathToUTF8(build_ninja_d_file.DirName().value()).c_str()))
        .PrintToStdout();
    return false;
  }

  // Replace existing build.ninja with just enough for ninja to call GN and
  // regenerate ninja files.
  if (!commands::PrepareForRegeneration(&setup->build_settings())) {
    return false;
  }

  // Erase everything but (user-created) args.gn, its imports and the
  // build.ninja files we just wrote.
  std::vector<base::FilePath::StringType> remaining{
      FILE_PATH_LITERAL("args.gn"),
      FILE_PATH_LITERAL("build.ninja"),
      FILE_PATH_LITERAL("build.ninja.d"),
  };
  for (const auto& f :
       setup->build_settings().build_args().build_args_dependency_files()) {
    if (IsStringInOutputDir(setup->build_settings().build_dir(), f.value())) {
      remaining.push_back(UTF8ToFilePath(f.GetName()).value());
    }
  }

  base::FileEnumerator traversal(
      build_dir, false,
      base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
  for (base::FilePath current = traversal.Next(); !current.empty();
       current = traversal.Next()) {
    base::FilePath::StringType basename =
        base::ToLowerASCII(current.BaseName().value());
    if (std::none_of(std::begin(remaining), std::end(remaining),
                     [&](auto rem) { return basename == rem; })) {
      base::DeleteFile(current, true);
    }
  }

  return true;
}

}  // namespace

namespace commands {

const char kClean[] = "clean";
const char kClean_HelpShort[] = "clean: Cleans the output directory.";
const char kClean_Help[] =
    "gn clean <out_dir>...\n"
    "\n"
    "  Deletes the contents of the output directory except for args.gn and\n"
    "  creates a Ninja build environment sufficient to regenerate the build.\n";

int RunClean(const std::vector<std::string>& args) {
  if (args.empty()) {
    Err(Location(), "Missing argument.", "Usage: \"gn clean <out_dir>...\"")
        .PrintToStdout();
    return 1;
  }

  for (const auto& dir : args) {
    if (!CleanOneDir(dir))
      return 1;
  }

  return 0;
}

}  // namespace commands