// Copyright 2016 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/bundle_data_target_generator.h"

#include "gn/parse_tree.h"
#include "gn/scope.h"
#include "gn/substitution_type.h"
#include "gn/target.h"
#include "gn/value.h"
#include "gn/variables.h"

BundleDataTargetGenerator::BundleDataTargetGenerator(
    Target* target,
    Scope* scope,
    const FunctionCallNode* function_call,
    Err* err)
    : TargetGenerator(target, scope, function_call, err) {}

BundleDataTargetGenerator::~BundleDataTargetGenerator() = default;

void BundleDataTargetGenerator::DoRun() {
  target_->set_output_type(Target::BUNDLE_DATA);

  if (!FillSources())
    return;
  if (!FillOutputs())
    return;
  if (!FillProductType())
    return;

  if (target_->sources().empty()) {
    *err_ = Err(function_call_,
                "Empty sources for bundle_data target."
                "You have to specify at least one file in the \"sources\".");
    return;
  }
  if (target_->action_values().outputs().list().size() != 1) {
    *err_ = Err(
        function_call_, "Target bundle_data must have exactly one output.",
        "You must specify exactly one value in the \"output\" array for the"
        "destination\ninto the generated bundle (see \"gn help bundle_data\"). "
        "If there are multiple\nsources to copy, use source expansion (see "
        "\"gn help source_expansion\").");
    return;
  }
}

bool BundleDataTargetGenerator::FillOutputs() {
  const Value* value = scope_->GetValue(variables::kOutputs, true);
  if (!value)
    return true;

  SubstitutionList& outputs = target_->action_values().outputs();
  if (!outputs.Parse(*value, err_))
    return false;

  // Check the substitutions used are valid for this purpose.
  for (const Substitution* type : outputs.required_types()) {
    if (!IsValidBundleDataSubstitution(type)) {
      *err_ = Err(value->origin(), "Invalid substitution type.",
                  "The substitution " + std::string(type->name) +
                      " isn't valid for something\n"
                      "operating on a bundle_data file such as this.");
      return false;
    }
  }

  // Validate that outputs are in the bundle.
  CHECK(outputs.list().size() == value->list_value().size());
  for (size_t i = 0; i < outputs.list().size(); i++) {
    if (!EnsureSubstitutionIsInBundleDir(outputs.list()[i],
                                         value->list_value()[i]))
      return false;
  }

  return true;
}

bool BundleDataTargetGenerator::FillProductType() {
  const Value* value = scope_->GetValue(variables::kProductType, true);
  if (!value)
    return true;

  if (!value->VerifyTypeIs(Value::STRING, err_))
    return false;

  target_->bundle_data().product_type().assign(value->string_value());
  return true;
}

bool BundleDataTargetGenerator::EnsureSubstitutionIsInBundleDir(
    const SubstitutionPattern& pattern,
    const Value& original_value) {
  if (pattern.ranges().empty()) {
    // Pattern is empty, error out (this prevents weirdness below).
    *err_ = Err(original_value, "This has an empty value in it.");
    return false;
  }

  if (SubstitutionIsInBundleDir(pattern.ranges()[0].type))
    return true;

  *err_ = Err(original_value, "File is not inside bundle directory.",
              "The given file should be in the output directory. Normally you\n"
              "would specify {{bundle_resources_dir}} or such substitution.");
  return false;
}