/*
 * Copyright (C) 2018 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.
 */

#ifndef AAPT2_DUMP_H
#define AAPT2_DUMP_H

#include <androidfw/FileStream.h>
#include <io/ZipArchive.h>

#include "Command.h"
#include "Debug.h"
#include "LoadedApk.h"
#include "dump/DumpManifest.h"

namespace aapt {

/**
 * The base command for dumping information about apks. When the command is executed, the command
 * performs the DumpApkCommand::Dump() operation on each apk provided as a file argument.
 **/
class DumpApkCommand : public Command {
 public:
  explicit DumpApkCommand(const std::string&& name, text::Printer* printer,
                          android::IDiagnostics* diag)
      : Command(name), printer_(printer), diag_(diag) {
    SetDescription("Dump information about an APK or APC.");
  }

  text::Printer* GetPrinter() {
    return printer_;
  }

  android::IDiagnostics* GetDiagnostics() {
    return diag_;
  }

  std::optional<std::string> GetPackageName(LoadedApk* apk) {
    xml::Element* manifest_el = apk->GetManifest()->root.get();
    if (!manifest_el) {
      GetDiagnostics()->Error(android::DiagMessage() << "No AndroidManifest.");
      return {};
    }

    xml::Attribute* attr = manifest_el->FindAttribute({}, "package");
    if (!attr) {
      GetDiagnostics()->Error(android::DiagMessage() << "No package name.");
      return {};
    }
    return attr->value;
  }

  /** Perform the dump operation on the apk. */
  virtual int Dump(LoadedApk* apk) = 0;

  int Action(const std::vector<std::string>& args) final {
    if (args.size() < 1) {
      diag_->Error(android::DiagMessage() << "No dump apk specified.");
      return 1;
    }

    bool error = false;
    for (auto apk : args) {
      auto loaded_apk = LoadedApk::LoadApkFromPath(apk, diag_);
      if (!loaded_apk) {
        error = true;
        continue;
      }

      error |= Dump(loaded_apk.get());
    }

    return error;
  }

 private:
  text::Printer* printer_;
  android::IDiagnostics* diag_;
};

/** Command that prints contents of files generated from the compilation stage. */
class DumpAPCCommand : public Command {
 public:
  explicit DumpAPCCommand(text::Printer* printer, android::IDiagnostics* diag)
      : Command("apc"), printer_(printer), diag_(diag) {
    SetDescription("Print the contents of the AAPT2 Container (APC) generated fom compilation.");
    AddOptionalSwitch("--no-values", "Suppresses output of values when displaying resource tables.",
                      &no_values_);
    AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
  }

  int Action(const std::vector<std::string>& args) override;

 private:
  text::Printer* printer_;
  android::IDiagnostics* diag_;
  bool no_values_ = false;
  bool verbose_ = false;
};

/** Easter egg command shown when users enter "badger" instead of "badging". */
class DumpBadgerCommand : public Command {
 public:
  explicit DumpBadgerCommand(text::Printer* printer) : Command("badger"), printer_(printer) {
  }

  int Action(const std::vector<std::string>& args) override;

 private:
  text::Printer* printer_;
  const static char kBadgerData[2925];
};

class DumpBadgingCommand : public DumpApkCommand {
 public:
  explicit DumpBadgingCommand(text::Printer* printer, android::IDiagnostics* diag)
      : DumpApkCommand("badging", printer, diag) {
    SetDescription("Print information extracted from the manifest of the APK.");
    AddOptionalSwitch("--include-meta-data", "Include meta-data information.",
                      &options_.include_meta_data);
  }

  void SetIncludeMetaData(bool value) {
    options_.include_meta_data = value;
  }

  void SetOnlyPermissions(bool value) {
    options_.only_permissions = value;
  }

  int Dump(LoadedApk* apk) override {
    return DumpManifest(apk, options_, GetPrinter(), GetDiagnostics());
  }

 private:
  DumpManifestOptions options_;
};

class DumpConfigsCommand : public DumpApkCommand {
 public:
  explicit DumpConfigsCommand(text::Printer* printer, android::IDiagnostics* diag)
      : DumpApkCommand("configurations", printer, diag) {
    SetDescription("Print every configuration used by a resource in the APK.");
  }

  int Dump(LoadedApk* apk) override;
};

class DumpPackageNameCommand : public DumpApkCommand {
 public:
  explicit DumpPackageNameCommand(text::Printer* printer, android::IDiagnostics* diag)
      : DumpApkCommand("packagename", printer, diag) {
    SetDescription("Print the package name of the APK.");
  }

  int Dump(LoadedApk* apk) override;
};

class DumpPermissionsCommand : public DumpApkCommand {
 public:
  explicit DumpPermissionsCommand(text::Printer* printer, android::IDiagnostics* diag)
      : DumpApkCommand("permissions", printer, diag) {
    SetDescription("Print the permissions extracted from the manifest of the APK.");
  }

  int Dump(LoadedApk* apk) override {
    DumpManifestOptions options;
    options.only_permissions = true;
    return DumpManifest(apk, options, GetPrinter(), GetDiagnostics());
  }
};

class DumpStringsCommand : public DumpApkCommand {
 public:
  explicit DumpStringsCommand(text::Printer* printer, android::IDiagnostics* diag)
      : DumpApkCommand("strings", printer, diag) {
    SetDescription("Print the contents of the resource table string pool in the APK.");
  }

  int Dump(LoadedApk* apk) override;
};

/** Prints the graph of parents of a style in an APK. */
class DumpStyleParentCommand : public DumpApkCommand {
 public:
  explicit DumpStyleParentCommand(text::Printer* printer, android::IDiagnostics* diag)
      : DumpApkCommand("styleparents", printer, diag) {
    SetDescription("Print the parents of a style in an APK.");
    AddRequiredFlag("--style", "The name of the style to print", &style_);
  }

  int Dump(LoadedApk* apk) override;

 private:
  std::string style_;
};

class DumpTableCommand : public DumpApkCommand {
 public:
  explicit DumpTableCommand(text::Printer* printer, android::IDiagnostics* diag)
      : DumpApkCommand("resources", printer, diag) {
    SetDescription("Print the contents of the resource table from the APK.");
    AddOptionalSwitch("--no-values", "Suppresses output of values when displaying resource tables.",
                      &no_values_);
    AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
  }

  int Dump(LoadedApk* apk) override;

 private:
  bool no_values_ = false;
  bool verbose_ = false;
};

class DumpXmlStringsCommand : public DumpApkCommand {
 public:
  explicit DumpXmlStringsCommand(text::Printer* printer, android::IDiagnostics* diag)
      : DumpApkCommand("xmlstrings", printer, diag) {
    SetDescription("Print the string pool of a compiled xml in an APK.");
    AddRequiredFlagList("--file", "A compiled xml file to print", &files_);
  }

  int Dump(LoadedApk* apk) override;

 private:
  std::vector<std::string> files_;
};

class DumpChunks : public DumpApkCommand {
 public:
  DumpChunks(text::Printer* printer, android::IDiagnostics* diag)
      : DumpApkCommand("chunks", printer, diag) {
    SetDescription("Print the chunk information of the compiled resources.arsc in the APK.");
  }

  int Dump(LoadedApk* apk) override;
};

/** Prints the tree of a compiled xml in an APK. */
class DumpXmlTreeCommand : public DumpApkCommand {
 public:
  explicit DumpXmlTreeCommand(text::Printer* printer, android::IDiagnostics* diag)
      : DumpApkCommand("xmltree", printer, diag) {
    SetDescription("Print the tree of a compiled xml in an APK.");
    AddRequiredFlagList("--file", "A compiled xml file to print", &files_);
  }

  int Dump(LoadedApk* apk) override;

 private:
  std::vector<std::string> files_;
};

class DumpOverlayableCommand : public DumpApkCommand {
 public:
  explicit DumpOverlayableCommand(text::Printer* printer, android::IDiagnostics* diag)
      : DumpApkCommand("overlayable", printer, diag) {
    SetDescription("Print the <overlayable> resources of an APK.");
  }

  int Dump(LoadedApk* apk) override;
};

/** The default dump command. Performs no action because a subcommand is required. */
class DumpCommand : public Command {
 public:
  explicit DumpCommand(text::Printer* printer, android::IDiagnostics* diag)
      : Command("dump", "d"), diag_(diag) {
    AddOptionalSubcommand(util::make_unique<DumpAPCCommand>(printer, diag_));
    AddOptionalSubcommand(util::make_unique<DumpBadgingCommand>(printer, diag_));
    AddOptionalSubcommand(util::make_unique<DumpConfigsCommand>(printer, diag_));
    AddOptionalSubcommand(util::make_unique<DumpPackageNameCommand>(printer, diag_));
    AddOptionalSubcommand(util::make_unique<DumpPermissionsCommand>(printer, diag_));
    AddOptionalSubcommand(util::make_unique<DumpStringsCommand>(printer, diag_));
    AddOptionalSubcommand(util::make_unique<DumpStyleParentCommand>(printer, diag_));
    AddOptionalSubcommand(util::make_unique<DumpTableCommand>(printer, diag_));
    AddOptionalSubcommand(util::make_unique<DumpChunks>(printer, diag_));
    AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_));
    AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_));
    AddOptionalSubcommand(util::make_unique<DumpOverlayableCommand>(printer, diag_));
    AddOptionalSubcommand(util::make_unique<DumpBadgerCommand>(printer), /* hidden */ true);
  }

  int Action(const std::vector<std::string>& args) override {
    if (args.size() == 0) {
      diag_->Error(android::DiagMessage() << "no subcommand specified");
    } else {
      diag_->Error(android::DiagMessage() << "unknown subcommand '" << args[0] << "'");
    }
    Usage(&std::cerr);
    return 1;
  }

 private:
  android::IDiagnostics* diag_;
};

}  // namespace aapt

#endif  // AAPT2_DUMP_H
