// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/zucchini/zucchini_commands.h"

#include <stddef.h>
#include <stdint.h>

#include <ostream>
#include <string>
#include <utility>

#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "components/zucchini/buffer_view.h"
#include "components/zucchini/crc32.h"
#include "components/zucchini/encoded_view.h"
#include "components/zucchini/image_index.h"
#include "components/zucchini/io_utils.h"
#include "components/zucchini/mapped_file.h"
#include "components/zucchini/patch_writer.h"
#include "components/zucchini/suffix_array.h"
#include "components/zucchini/zucchini_integration.h"
#include "components/zucchini/zucchini_tools.h"

namespace {

using zucchini::status::kStatusFileReadError;
using zucchini::status::kStatusSuccess;

/******** Command-line Switches ********/

constexpr char kSwitchDump[] = "dump";
constexpr char kSwitchImpose[] = "impose";
constexpr char kSwitchKeep[] = "keep";
constexpr char kSwitchRaw[] = "raw";
constexpr char kSwitchStartScanAt[] = "start-scan-at";

class WrappedMappedFileReader : public zucchini::MappedFileReader {
 public:
  static constexpr uint32_t read_flags = base::File::FLAG_OPEN |
                                         base::File::FLAG_READ |
                                         base::File::FLAG_WIN_SHARE_DELETE;

  explicit WrappedMappedFileReader(const base::FilePath& path)
      : MappedFileReader(base::File(path, read_flags)) {
    if (HasError()) {
      LOG(ERROR) << "Error reading file " << path.value() << ": " << error();
      status = kStatusFileReadError;
    }
  }

  zucchini::status::Code status = kStatusSuccess;
};

zucchini::GenerateOptions ReadGenerateOptions(const MainParams& params) {
  zucchini::GenerateOptions options{
      .imposed_matches =
          params.command_line->GetSwitchValueASCII(kSwitchImpose),
      .is_raw = params.command_line->HasSwitch(kSwitchRaw),
  };
  if (params.command_line->HasSwitch(kSwitchStartScanAt)) {
    const std::string s =
        params.command_line->GetSwitchValueASCII(kSwitchStartScanAt);
    std::istringstream(s) >> options.start_scan_at;
  }
  return options;
}

}  // namespace

/******** Various Main*() functions ********/

zucchini::status::Code MainGen(MainParams params) {
  zucchini::GenerateOptions options = ReadGenerateOptions(params);
  CHECK_EQ(3U, params.file_paths->size());
  return zucchini::Generate((*params.file_paths)[0], (*params.file_paths)[1],
                            (*params.file_paths)[2], options,
                            params.command_line->HasSwitch(kSwitchKeep));
}

zucchini::status::Code MainApply(MainParams params) {
  CHECK_EQ(3U, params.file_paths->size());
  return zucchini::Apply((*params.file_paths)[0], (*params.file_paths)[1],
                         (*params.file_paths)[2],
                         params.command_line->HasSwitch(kSwitchKeep));
}

zucchini::status::Code MainVerify(MainParams params) {
  CHECK_EQ(1U, params.file_paths->size());
  return zucchini::VerifyPatch((*params.file_paths)[0]);
}

zucchini::status::Code MainRead(MainParams params) {
  CHECK_EQ(1U, params.file_paths->size());
  WrappedMappedFileReader input((*params.file_paths)[0]);
  if (input.status != kStatusSuccess)
    return input.status;

  bool do_dump = params.command_line->HasSwitch(kSwitchDump);
  zucchini::status::Code status =
      zucchini::ReadReferences(input.region(), do_dump, *params.out);
  if (status != kStatusSuccess)
    *params.err << "Fatal error found when dumping references." << std::endl;
  return status;
}

zucchini::status::Code MainDetect(MainParams params) {
  zucchini::GenerateOptions options = ReadGenerateOptions(params);
  CHECK_EQ(1U, params.file_paths->size());
  WrappedMappedFileReader input((*params.file_paths)[0]);
  if (input.status != kStatusSuccess)
    return input.status;

  std::vector<zucchini::ConstBufferView> sub_image_list;
  zucchini::status::Code result = zucchini::DetectAll(
      input.region(), options, *params.out, &sub_image_list);
  if (result != kStatusSuccess)
    *params.err << "Fatal error found when detecting executables." << std::endl;
  return result;
}

zucchini::status::Code MainMatch(MainParams params) {
  zucchini::GenerateOptions options = ReadGenerateOptions(params);
  CHECK_EQ(2U, params.file_paths->size());
  WrappedMappedFileReader old_image((*params.file_paths)[0]);
  if (old_image.status != kStatusSuccess)
    return old_image.status;
  WrappedMappedFileReader new_image((*params.file_paths)[1]);
  if (new_image.status != kStatusSuccess)
    return new_image.status;

  zucchini::status::Code status = zucchini::MatchAll(
      old_image.region(), new_image.region(), options, *params.out);
  if (status != kStatusSuccess)
    *params.err << "Fatal error found when matching executables." << std::endl;
  return status;
}

zucchini::status::Code MainCrc32(MainParams params) {
  CHECK_EQ(1U, params.file_paths->size());
  WrappedMappedFileReader input((*params.file_paths)[0]);
  if (input.status != kStatusSuccess)
    return input.status;

  uint32_t crc =
      zucchini::CalculateCrc32(input.region().begin(), input.region().end());
  *params.out << "CRC32: " << zucchini::AsHex<8>(crc) << std::endl;
  return kStatusSuccess;
}

zucchini::status::Code MainSuffixArray(MainParams params) {
  CHECK_EQ(1U, params.file_paths->size());
  WrappedMappedFileReader input((*params.file_paths)[0]);
  if (input.status != kStatusSuccess)
    return input.status;

  zucchini::ImageIndex image_index(input.region());
  zucchini::EncodedView view_raw(image_index);
  std::vector<zucchini::offset_t> sa_raw =
      zucchini::MakeSuffixArray<zucchini::InducedSuffixSort>(view_raw,
                                                             size_t(256));
  return kStatusSuccess;
}
