// Copyright 2014 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/substitution_type.h"

#include <stddef.h>
#include <stdlib.h>

#include <algorithm>
#include <cstring>

#include "gn/c_substitution_type.h"
#include "gn/err.h"
#include "gn/rust_substitution_type.h"

const std::vector<SubstitutionTypes*> AllSubstitutions = {
    &GeneralSubstitutions, &CSubstitutions, &RustSubstitutions};

const SubstitutionTypes GeneralSubstitutions = {
    &SubstitutionLiteral,

    &SubstitutionOutput,
    &SubstitutionLabel,
    &SubstitutionLabelName,
    &SubstitutionLabelNoToolchain,
    &SubstitutionRootGenDir,
    &SubstitutionRootOutDir,
    &SubstitutionOutputDir,
    &SubstitutionOutputExtension,
    &SubstitutionTargetGenDir,
    &SubstitutionTargetOutDir,
    &SubstitutionTargetOutputName,

    &SubstitutionSource,
    &SubstitutionSourceNamePart,
    &SubstitutionSourceFilePart,
    &SubstitutionSourceDir,
    &SubstitutionSourceRootRelativeDir,
    &SubstitutionSourceGenDir,
    &SubstitutionSourceOutDir,
    &SubstitutionSourceTargetRelative,

    &SubstitutionBundleRootDir,
    &SubstitutionBundleContentsDir,
    &SubstitutionBundleResourcesDir,
    &SubstitutionBundleExecutableDir,

    &SubstitutionBundleProductType,
    &SubstitutionBundlePartialInfoPlist,
    &SubstitutionXcassetsCompilerFlags,

    &SubstitutionRspFileName,
};

const Substitution SubstitutionLiteral = {"<<literal>>", nullptr};

const Substitution SubstitutionSource = {"{{source}}", "in"};
const Substitution SubstitutionOutput = {"{{output}}", "out"};

const Substitution SubstitutionSourceNamePart = {"{{source_name_part}}",
                                                 "source_name_part"};
const Substitution SubstitutionSourceFilePart = {"{{source_file_part}}",
                                                 "source_file_part"};
const Substitution SubstitutionSourceDir = {"{{source_dir}}", "source_dir"};
const Substitution SubstitutionSourceRootRelativeDir = {
    "{{source_root_relative_dir}}", "source_root_relative_dir"};
const Substitution SubstitutionSourceGenDir = {"{{source_gen_dir}}",
                                               "source_gen_dir"};
const Substitution SubstitutionSourceOutDir = {"{{source_out_dir}}",
                                               "source_out_dir"};
const Substitution SubstitutionSourceTargetRelative = {
    "{{source_target_relative}}", "source_target_relative"};

// Valid for all compiler and linker tools. These depend on the target and
// do not vary on a per-file basis.
const Substitution SubstitutionLabel = {"{{label}}", "label"};
const Substitution SubstitutionLabelName = {"{{label_name}}", "label_name"};
const Substitution SubstitutionLabelNoToolchain = {"{{label_no_toolchain}}",
                                                   "label_no_toolchain"};
const Substitution SubstitutionRootGenDir = {"{{root_gen_dir}}",
                                             "root_gen_dir"};
const Substitution SubstitutionRootOutDir = {"{{root_out_dir}}",
                                             "root_out_dir"};
const Substitution SubstitutionOutputDir = {"{{output_dir}}", "output_dir"};
const Substitution SubstitutionOutputExtension = {"{{output_extension}}",
                                                  "output_extension"};
const Substitution SubstitutionTargetGenDir = {"{{target_gen_dir}}",
                                               "target_gen_dir"};
const Substitution SubstitutionTargetOutDir = {"{{target_out_dir}}",
                                               "target_out_dir"};
const Substitution SubstitutionTargetOutputName = {"{{target_output_name}}",
                                                   "target_output_name"};

// Valid for bundle_data targets.
const Substitution SubstitutionBundleRootDir = {"{{bundle_root_dir}}",
                                                "bundle_root_dir"};
const Substitution SubstitutionBundleContentsDir = {"{{bundle_contents_dir}}",
                                                    "bundle_contents_dir"};
const Substitution SubstitutionBundleResourcesDir = {"{{bundle_resources_dir}}",
                                                     "bundle_resources_dir"};
const Substitution SubstitutionBundleExecutableDir = {
    "{{bundle_executable_dir}}", "bundle_executable_dir"};

// Valid for compile_xcassets tool.
const Substitution SubstitutionBundleProductType = {"{{bundle_product_type}}",
                                                    "product_type"};
const Substitution SubstitutionBundlePartialInfoPlist = {
    "{{bundle_partial_info_plist}}", "partial_info_plist"};
const Substitution SubstitutionXcassetsCompilerFlags = {
    "{{xcasset_compiler_flags}}", "xcasset_compiler_flags"};

// Used only for the args of actions.
const Substitution SubstitutionRspFileName = {"{{response_file_name}}",
                                              "rspfile"};

SubstitutionBits::SubstitutionBits() = default;

void SubstitutionBits::MergeFrom(const SubstitutionBits& other) {
  for (const Substitution* s : other.used)
    used.insert(s);
}

void SubstitutionBits::FillVector(
    std::vector<const Substitution*>* vect) const {
  for (const Substitution* s : used) {
    vect->push_back(s);
  }
  // Sort by name for deterministic output across different builds of the
  // same GN source. The flat_set iterates by pointer address which can
  // vary between binaries.
  std::sort(vect->begin(), vect->end(),
            [](const Substitution* a, const Substitution* b) {
              return std::strcmp(a->name, b->name) < 0;
            });
}

bool SubstitutionIsInOutputDir(const Substitution* type) {
  return type == &SubstitutionSourceGenDir ||
         type == &SubstitutionSourceOutDir || type == &SubstitutionRootGenDir ||
         type == &SubstitutionRootOutDir || type == &SubstitutionTargetGenDir ||
         type == &SubstitutionTargetOutDir;
}

bool SubstitutionIsInBundleDir(const Substitution* type) {
  return type == &SubstitutionBundleRootDir ||
         type == &SubstitutionBundleContentsDir ||
         type == &SubstitutionBundleResourcesDir ||
         type == &SubstitutionBundleExecutableDir;
}

bool IsValidBundleDataSubstitution(const Substitution* type) {
  return type == &SubstitutionLiteral ||
         type == &SubstitutionSourceTargetRelative ||
         type == &SubstitutionSourceNamePart ||
         type == &SubstitutionSourceFilePart ||
         type == &SubstitutionSourceRootRelativeDir ||
         type == &SubstitutionBundleRootDir ||
         type == &SubstitutionBundleContentsDir ||
         type == &SubstitutionBundleResourcesDir ||
         type == &SubstitutionBundleExecutableDir;
}

bool IsValidSourceSubstitution(const Substitution* type) {
  return type == &SubstitutionLiteral || type == &SubstitutionSource ||
         type == &SubstitutionSourceNamePart ||
         type == &SubstitutionSourceFilePart ||
         type == &SubstitutionSourceDir ||
         type == &SubstitutionSourceRootRelativeDir ||
         type == &SubstitutionSourceGenDir ||
         type == &SubstitutionSourceOutDir ||
         type == &SubstitutionSourceTargetRelative;
}

bool IsValidScriptArgsSubstitution(const Substitution* type) {
  return IsValidSourceSubstitution(type) || type == &SubstitutionRspFileName ||
         IsValidCompilerScriptArgsSubstitution(type) ||
         IsValidRustScriptArgsSubstitution(type);
}

bool IsValidToolSubstitution(const Substitution* type) {
  return type == &SubstitutionLiteral || type == &SubstitutionOutput ||
         type == &SubstitutionLabel || type == &SubstitutionLabelName ||
         type == &SubstitutionLabelNoToolchain ||
         type == &SubstitutionRootGenDir || type == &SubstitutionRootOutDir ||
         type == &SubstitutionTargetGenDir ||
         type == &SubstitutionTargetOutDir ||
         type == &SubstitutionTargetOutputName;
}

bool IsValidCopySubstitution(const Substitution* type) {
  return IsValidToolSubstitution(type) || type == &SubstitutionSource;
}

bool IsValidCompileXCassetsSubstitution(const Substitution* type) {
  return IsValidToolSubstitution(type) || type == &CSubstitutionLinkerInputs ||
         type == &SubstitutionBundleProductType ||
         type == &SubstitutionBundlePartialInfoPlist ||
         type == &SubstitutionXcassetsCompilerFlags;
}

bool EnsureValidSubstitutions(const std::vector<const Substitution*>& types,
                              bool (*is_valid_subst)(const Substitution*),
                              const ParseNode* origin,
                              Err* err) {
  for (const Substitution* type : types) {
    if (!is_valid_subst(type)) {
      *err = Err(origin, "Invalid substitution type.",
                 "The substitution " + std::string(type->name) +
                     " isn't valid for something\n"
                     "operating on a source file such as this.");
      return false;
    }
  }
  return true;
}
