// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "repr/protobuf/abi_diff.h"
#include "repr/protobuf/abi_dump.h"

#include <google/protobuf/text_format.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>

#include <llvm/Support/CommandLine.h>
#include <llvm/Support/raw_ostream.h>

#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include <stdlib.h>


static llvm::cl::OptionCategory merge_diff_category(
    "merge-abi-diff options");

static llvm::cl::list<std::string> diff_report_list(
    llvm::cl::Positional, llvm::cl::desc("<diff-reports>"), llvm::cl::Required,
    llvm::cl::cat(merge_diff_category), llvm::cl::OneOrMore);

static llvm::cl::opt<std::string> merged_diff_report(
    "o", llvm::cl::desc("<merged-diff-report>"), llvm::cl::Required,
    llvm::cl::cat(merge_diff_category));

static llvm::cl::opt<bool> advice_only(
    "advice-only", llvm::cl::desc("Advisory mode only"), llvm::cl::Optional,
    llvm::cl::cat(merge_diff_category));

static llvm::cl::opt<bool> do_not_break_on_extensions(
    "allow-extensions",
    llvm::cl::desc("Do not return a non zero status on extensions"),
    llvm::cl::Optional, llvm::cl::cat(merge_diff_category));

static bool IsStatusDowngraded(
    const abi_diff::CompatibilityStatus &old_status,
    const abi_diff::CompatibilityStatus &new_status) {
  bool status_downgraded = false;
  switch (old_status) {
    case abi_diff::CompatibilityStatus::EXTENSION:
      if (new_status == abi_diff::CompatibilityStatus::INCOMPATIBLE) {
        status_downgraded = true;
      }
      break;
    case abi_diff::CompatibilityStatus::COMPATIBLE:
      if (new_status != abi_diff::CompatibilityStatus::COMPATIBLE) {
        status_downgraded = true;
      }
      break;
    default:
      break;
  }
  return status_downgraded;
}

static abi_diff::CompatibilityStatus MergeDiffReports(
    const std::vector<std::string> &diff_reports,
    const std::string &merged_diff_report) {
  abi_diff::MergedTranslationUnitDiff merged_tu_diff;
  std::ofstream text_output(merged_diff_report);
  google::protobuf::io::OstreamOutputStream text_os(&text_output);
  abi_diff::CompatibilityStatus status =
      abi_diff::CompatibilityStatus::COMPATIBLE;

  for (auto &&i : diff_reports) {
    abi_diff::TranslationUnitDiff diff_tu;
    std::ifstream input(i);
    google::protobuf::io::IstreamInputStream text_is(&input);
    if (!google::protobuf::TextFormat::Parse(&text_is, &diff_tu)) {
      llvm::errs() << "Failed to parse diff report\n";
      ::exit(1);
    }
    abi_diff::ConciseDiffReportInformation *added_tu_diff =
        merged_tu_diff.add_diff_reports();
    if (!added_tu_diff) {
      llvm::errs() << "Failed to add diff report to merged report\n";
      ::exit(1);
    }
    abi_diff::CompatibilityStatus new_status = diff_tu.compatibility_status();
    added_tu_diff->set_lib_name(diff_tu.lib_name());
    added_tu_diff->set_arch(diff_tu.arch());
    added_tu_diff->set_diff_report_path(i);
    added_tu_diff->set_compatibility_status(new_status);
    // Only, if the status is downgraded, change it.
    if (IsStatusDowngraded(status, new_status)) {
      status = new_status;
    }
  }

  if (!google::protobuf::TextFormat::Print(merged_tu_diff, &text_os)) {
    llvm::errs() << "Serialization to ostream failed\n";
    ::exit(1);
  }
  return status;
}

int main(int argc, const char **argv) {
  GOOGLE_PROTOBUF_VERIFY_VERSION;
  llvm::cl::ParseCommandLineOptions(argc, argv, "merge-abi-diff");
  abi_diff::CompatibilityStatus extension_or_incompatible =
      MergeDiffReports(diff_report_list, merged_diff_report);
  std::string status_str = "";
  switch (extension_or_incompatible) {
    case abi_diff::CompatibilityStatus::INCOMPATIBLE:
      status_str = "broken";
      break;
    case abi_diff::CompatibilityStatus::EXTENSION:
      status_str = "extended";
      break;
    default:
      break;
  }
  if (extension_or_incompatible) {
    llvm::errs() << "******************************************************\n"
                 << "VNDK Abi "
                 << status_str
                 << ":"
                 << " Please check compatibility report at : "
                 << merged_diff_report << "\n"
                 << "******************************************************\n";
  }

  if (do_not_break_on_extensions &&
      extension_or_incompatible == abi_diff::CompatibilityStatus::EXTENSION) {
      extension_or_incompatible = abi_diff::CompatibilityStatus::COMPATIBLE;
  }

  if (!advice_only) {
    return extension_or_incompatible;
  }
  return abi_diff::CompatibilityStatus::COMPATIBLE;
}