// Copyright 2020 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/swift_values.h"

#include <algorithm>

#include "gn/deps_iterator.h"
#include "gn/err.h"
#include "gn/settings.h"
#include "gn/substitution_writer.h"
#include "gn/target.h"
#include "gn/tool.h"

SwiftValues::SwiftValues() = default;

SwiftValues::~SwiftValues() = default;

// static
bool SwiftValues::OnTargetResolved(Target* target, Err* err) {
  if (!target->builds_swift_module())
    return true;

  return target->swift_values().FillModuleOutputFile(target, err);
}

const Tool* SwiftValues::GetTool(const Target* target) const {
  DCHECK(target->builds_swift_module());
  return target->toolchain()->GetToolForSourceType(SourceFile::SOURCE_SWIFT);
}

void SwiftValues::GetOutputs(const Target* target,
                             std::vector<OutputFile>* result) const {
  const Tool* tool = GetTool(target);

  // Expand tool's outputs().
  SubstitutionWriter::ApplyListToLinkerAsOutputFile(target, tool,
                                                    tool->outputs(), result);

  // Expand tool's partial_outputs() for each .swift source file.
  for (const SourceFile& source : target->sources()) {
    if (!source.IsSwiftType()) {
      continue;
    }

    SubstitutionWriter::ApplyListToCompilerAsOutputFile(
        target, source, tool->partial_outputs(), result);
  }
}

void SwiftValues::GetOutputsAsSourceFiles(
    const Target* target,
    std::vector<SourceFile>* result) const {
  std::vector<OutputFile> outputs;
  GetOutputs(target, &outputs);

  result->reserve(outputs.size());

  const BuildSettings* build_settings = target->settings()->build_settings();
  for (const OutputFile& output : outputs) {
    result->push_back(output.AsSourceFile(build_settings));
  }
}

bool SwiftValues::FillModuleOutputFile(Target* target, Err* err) {
  std::vector<OutputFile> outputs;
  GetOutputs(target, &outputs);

  // Keep only .swiftmodule output files.
  const BuildSettings* build_settings = target->settings()->build_settings();
  const auto iter = std::remove_if(
      outputs.begin(), outputs.end(),
      [build_settings](const OutputFile& output) -> bool {
        SourceFile output_as_source_file = output.AsSourceFile(build_settings);
        return !output_as_source_file.IsSwiftModuleType();
      });
  outputs.erase(iter, outputs.end());

  // A target should generate exactly one .swiftmodule file.
  if (outputs.size() != 1) {
    const Tool* tool = GetTool(target);
    *err = Err(tool->defined_from(), "Incorrect outputs for tool",
               "The outputs of tool " + std::string(tool->name()) +
                   " must list exactly one .swiftmodule file");
    return false;
  }

  module_output_file_ = outputs.front();
  module_output_dir_ =
      module_output_file_.AsSourceFile(build_settings).GetDir();

  return true;
}