// Copyright 2017 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 "tools/traffic_annotation/auditor/auditor_result.h"

#include "base/strings/stringprintf.h"

const int AuditorResult::kNoCodeLineSpecified = -1;

AuditorResult::AuditorResult(Type type,
                             const std::string& message,
                             const std::string& file_path,
                             int line)
    : type_(type), file_path_(file_path), line_(line) {
  DCHECK(line != kNoCodeLineSpecified ||
         type == AuditorResult::Type::RESULT_OK ||
         type == AuditorResult::Type::RESULT_IGNORE ||
         type == AuditorResult::Type::ERROR_FATAL ||
         type == AuditorResult::Type::ERROR_HASH_CODE_COLLISION ||
         type == AuditorResult::Type::ERROR_REPEATED_ID ||
         type == AuditorResult::Type::ERROR_MERGE_FAILED ||
         type == AuditorResult::Type::ERROR_ANNOTATIONS_XML_UPDATE ||
         type == AuditorResult::Type::ERROR_INVALID_OS);
  DCHECK(!message.empty() || type == AuditorResult::Type::RESULT_OK ||
         type == AuditorResult::Type::RESULT_IGNORE ||
         type == AuditorResult::Type::ERROR_MISSING_TAG_USED ||
         type == AuditorResult::Type::ERROR_NO_ANNOTATION ||
         type == AuditorResult::Type::ERROR_MISSING_SECOND_ID ||
         type == AuditorResult::Type::ERROR_DIRECT_ASSIGNMENT ||
         type == AuditorResult::Type::ERROR_TEST_ANNOTATION ||
         type == AuditorResult::Type::ERROR_INVALID_OS);
  if (!message.empty())
    details_.push_back(message);
};

AuditorResult::AuditorResult(Type type, const std::string& message)
    : AuditorResult::AuditorResult(type,
                                   message,
                                   std::string(),
                                   kNoCodeLineSpecified) {}

AuditorResult::AuditorResult(Type type)
    : AuditorResult::AuditorResult(type,
                                   std::string(),
                                   std::string(),
                                   kNoCodeLineSpecified) {}

AuditorResult::AuditorResult(const AuditorResult& other) = default;
;

AuditorResult::~AuditorResult() = default;

void AuditorResult::AddDetail(const std::string& message) {
  details_.push_back(message);
}

std::string AuditorResult::ToText() const {
  switch (type_) {
    case AuditorResult::Type::ERROR_FATAL:
      DCHECK(!details_.empty());
      return details_[0];

    case AuditorResult::Type::ERROR_MISSING_TAG_USED:
      return base::StringPrintf(
          "MISSING_TRAFFIC_ANNOTATION tag used in '%s', line %i.",
          file_path_.c_str(), line_);

    case AuditorResult::Type::ERROR_NO_ANNOTATION:
      return base::StringPrintf("Empty annotation in '%s', line %i.",
                                file_path_.c_str(), line_);

    case AuditorResult::Type::ERROR_SYNTAX: {
      DCHECK(!details_.empty());
      std::string flat_message(details_[0]);
      std::replace(flat_message.begin(), flat_message.end(), '\n', ' ');
      return base::StringPrintf("Syntax error in '%s': %s", file_path_.c_str(),
                                flat_message.c_str());
    }

    case AuditorResult::Type::ERROR_RESERVED_ID_HASH_CODE:
      DCHECK(!details_.empty());
      return base::StringPrintf(
          "Id '%s' in '%s:%i' has a hash code equal to a reserved word and "
          "should be changed.",
          details_[0].c_str(), file_path_.c_str(), line_);

    case AuditorResult::Type::ERROR_DEPRECATED_ID_HASH_CODE:
      DCHECK(!details_.empty());
      return base::StringPrintf(
          "Id '%s' in '%s:%i' has a hash code equal to a deprecated id and "
          "should be changed.",
          details_[0].c_str(), file_path_.c_str(), line_);

    case AuditorResult::Type::ERROR_HASH_CODE_COLLISION:
      DCHECK_EQ(details_.size(), 2u);
      return base::StringPrintf(
          "The following annotations have colliding hash codes and should be "
          "updated: %s, %s.",
          details_[0].c_str(), details_[1].c_str());

    case AuditorResult::Type::ERROR_REPEATED_ID:
      DCHECK_EQ(details_.size(), 2u);
      return base::StringPrintf(
          "The following annotations have equal ids and should be updated: "
          "%s, %s.",
          details_[0].c_str(), details_[1].c_str());

    case AuditorResult::Type::ERROR_ID_INVALID_CHARACTER:
      DCHECK(!details_.empty());
      return base::StringPrintf(
          "Id '%s' in '%s:%i' contains an invalid character.",
          details_[0].c_str(), file_path_.c_str(), line_);

    case AuditorResult::Type::ERROR_MISSING_ANNOTATION:
      DCHECK(!details_.empty());
      return base::StringPrintf("Function '%s' in '%s:%i' requires annotation.",
                                details_[0].c_str(), file_path_.c_str(), line_);

    case AuditorResult::Type::ERROR_INCOMPLETE_ANNOTATION:
      DCHECK(!details_.empty());
      return base::StringPrintf(
          "Annotation at '%s:%i' has the following missing fields: %s",
          file_path_.c_str(), line_, details_[0].c_str());

    case AuditorResult::Type::ERROR_MISSING_SECOND_ID:
      return base::StringPrintf(
          "Second id of annotation at '%s:%i' should be updated as it has the "
          "same hash code as the first one.",
          file_path_.c_str(), line_);

    case AuditorResult::Type::ERROR_INCONSISTENT_ANNOTATION:
      DCHECK(!details_.empty());
      return base::StringPrintf(
          "Annotation at '%s:%i' has the following inconsistencies: %s",
          file_path_.c_str(), line_, details_[0].c_str());

    case AuditorResult::Type::ERROR_MERGE_FAILED:
      DCHECK(details_.size() == 3);
      return base::StringPrintf(
          "Annotations '%s' and '%s' cannot be merged due to the following "
          "error(s): %s",
          details_[1].c_str(), details_[2].c_str(), details_[0].c_str());

    case AuditorResult::Type::ERROR_INCOMPLETED_ANNOTATION:
      DCHECK(!details_.empty());
      return base::StringPrintf("Annotation '%s' is never completed.",
                                details_[0].c_str());

    case AuditorResult::Type::ERROR_DIRECT_ASSIGNMENT:
      return base::StringPrintf(
          "Annotation at '%s:%i' is assigned without annotations API "
          "functions.",
          file_path_.c_str(), line_);

    case AuditorResult::Type::ERROR_ANNOTATIONS_XML_UPDATE:
      DCHECK(!details_.empty());
      return base::StringPrintf(
          "'tools/traffic_annotation/summary/annotations.xml' requires update. "
          "It is recommended to run traffic_annotation_auditor locally to do "
          "the updates automatically (please refer to tools/traffic_annotation/"
          "auditor/README.md), but you can also apply the following edit(s) to "
          "do it manually:%s\n\n If you are using build flags that modify "
          "files (like jumbo), rerun the auditor using --all-files switch.",
          details_[0].c_str());

    case AuditorResult::Type::ERROR_TEST_ANNOTATION:
      return base::StringPrintf("Annotation for tests is used in '%s:%i'.",
                                file_path_.c_str(), line_);

    case AuditorResult::Type::ERROR_INVALID_OS:
      return base::StringPrintf("Invalid OS '%s' in annotation '%s' at %s",
                                details_[0].c_str(), details_[1].c_str(),
                                file_path_.c_str());

    case AuditorResult::Type::ERROR_DEPRECATED_WITH_OS:
      return base::StringPrintf(
          "Annotation '%s' has a deprecation date and at least one active OS "
          "at %s.",
          details_[0].c_str(), file_path_.c_str());

    default:
      return std::string();
  }
}

std::string AuditorResult::ToShortText() const {
  switch (type_) {
    case AuditorResult::Type::ERROR_INCOMPLETE_ANNOTATION:
      DCHECK(!details_.empty());
      return base::StringPrintf("the following fields are missing: %s",
                                details_[0].c_str());

    case AuditorResult::Type::ERROR_INCONSISTENT_ANNOTATION:
      DCHECK(!details_.empty());
      return base::StringPrintf("the following inconsistencies: %s",
                                details_[0].c_str());

    default:
      NOTREACHED();
      return std::string();
  }
}
