/*
 * 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.
 */

#include <cstdint>
#include <iostream>
#include <set>
#include <sstream>

#include <android-base/file.h>

#include "dexanalyze_bytecode.h"
#include "dexanalyze_experiments.h"
#include "dexanalyze_strings.h"
#include "dex/code_item_accessors-inl.h"
#include "dex/dex_file.h"
#include "dex/dex_file_loader.h"
#include "dex/dex_instruction-inl.h"

namespace art {
namespace dexanalyze {

class DexAnalyze {
  static constexpr int kExitCodeUsageError = 1;
  static constexpr int kExitCodeFailedToOpenFile = 2;
  static constexpr int kExitCodeFailedToOpenDex = 3;
  static constexpr int kExitCodeFailedToProcessDex = 4;

  static void StdoutLogger(android::base::LogId,
                           android::base::LogSeverity,
                           const char*,
                           const char*,
                           unsigned int,
                           const char* message) {
    std::cout << message << std::endl;
  }

  static int Usage(char** argv) {
    LOG(ERROR)
        << "Usage " << argv[0] << " [options] <dex files>\n"
        << "    [options] is a combination of the following\n"
        << "    -count_indices (Count dex indices accessed from code items)\n"
        << "    -analyze-strings (Analyze string data)\n"
        << "    -analyze-debug-info (Analyze debug info)\n"
        << "    -new-bytecode (Bytecode optimizations)\n"
        << "    -i (Ignore Dex checksum and verification failures)\n"
        << "    -a (Run all experiments)\n"
        << "    -n <int> (run experiment with 1 .. n as argument)\n"
        << "    -d (Dump on per Dex basis)\n"
        << "    -v (quiet(0) to everything(2))\n";
    return kExitCodeUsageError;
  }

  struct Options {
    int Parse(int argc, char** argv) {
      int i;
      for (i = 1; i < argc; ++i) {
        const std::string arg = argv[i];
        if (arg == "-i") {
          verify_checksum_ = false;
          run_dex_file_verifier_ = false;
        } else if (arg == "-v") {
          if (i + 1 >= argc) {
            return Usage(argv);
          }
          std::istringstream iss(argv[i + 1]);
          size_t verbose_level = 0u;
          iss >> verbose_level;
          if (verbose_level > static_cast<size_t>(VerboseLevel::kEverything)) {
            return Usage(argv);
          }
          ++i;
          verbose_level_ = static_cast<VerboseLevel>(verbose_level);
        } else if (arg == "-a") {
          run_all_experiments_ = true;
        } else if (arg == "-n") {
          if (i + 1 >= argc) {
            return Usage(argv);
          }
          std::istringstream iss(argv[i + 1]);
          iss >> experiment_max_;
          ++i;
        } else if (arg == "-count-indices") {
          exp_count_indices_ = true;
        } else if (arg == "-analyze-strings") {
          exp_analyze_strings_ = true;
        } else if (arg == "-analyze-debug-info") {
          exp_debug_info_ = true;
        } else if (arg == "-new-bytecode") {
          exp_bytecode_ = true;
        } else if (arg == "-d") {
          dump_per_input_dex_ = true;
        } else if (!arg.empty() && arg[0] == '-') {
          return Usage(argv);
        } else {
          break;
        }
      }
      filenames_.insert(filenames_.end(), argv + i, argv + argc);
      if (filenames_.empty()) {
        return Usage(argv);
      }
      return 0;
    }

    VerboseLevel verbose_level_ = VerboseLevel::kNormal;
    bool verify_checksum_ = true;
    bool run_dex_file_verifier_ = true;
    bool dump_per_input_dex_ = false;
    bool exp_count_indices_ = false;
    bool exp_code_metrics_ = false;
    bool exp_analyze_strings_ = false;
    bool exp_debug_info_ = false;
    bool exp_bytecode_ = false;
    bool run_all_experiments_ = false;
    uint64_t experiment_max_ = 1u;
    std::vector<std::string> filenames_;
  };

  class Analysis {
   public:
    explicit Analysis(const Options* options) : options_(options) {
      if (options->run_all_experiments_ || options->exp_count_indices_) {
        experiments_.emplace_back(new CountDexIndices);
      }
      if (options->run_all_experiments_ || options->exp_analyze_strings_) {
        experiments_.emplace_back(new AnalyzeStrings);
      }
      if (options->run_all_experiments_ || options->exp_code_metrics_) {
        experiments_.emplace_back(new CodeMetrics);
      }
      if (options->run_all_experiments_ || options->exp_debug_info_) {
        experiments_.emplace_back(new AnalyzeDebugInfo);
      }
      if (options->run_all_experiments_ || options->exp_bytecode_) {
        for (size_t i = 0; i < options->experiment_max_; ++i) {
          uint64_t exp_value = 0u;
          if (i == 0) {
            exp_value = std::numeric_limits<uint64_t>::max();
          } else if (i == 1) {
            exp_value = 0u;
          } else {
            exp_value = 1u << (i - 2);
          }
          experiments_.emplace_back(new NewRegisterInstructions(exp_value));
        }
      }
      for (const std::unique_ptr<Experiment>& experiment : experiments_) {
        experiment->verbose_level_ = options->verbose_level_;
      }
    }

    bool ProcessDexFiles(const std::vector<std::unique_ptr<const DexFile>>& dex_files) {
      for (std::unique_ptr<Experiment>& experiment : experiments_) {
        experiment->ProcessDexFiles(dex_files);
      }
      for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
        total_size_ += dex_file->Size();
      }
      dex_count_ += dex_files.size();
      return true;
    }

    void Dump(std::ostream& os) {
      for (std::unique_ptr<Experiment>& experiment : experiments_) {
        experiment->Dump(os, total_size_);
        os << "\n";
      }
    }

    const Options* const options_;
    std::vector<std::unique_ptr<Experiment>> experiments_;
    size_t dex_count_ = 0;
    uint64_t total_size_ = 0u;
  };

 public:
  static int Run(int argc, char** argv) {
    android::base::SetLogger(StdoutLogger);

    Options options;
    int result = options.Parse(argc, argv);
    if (result != 0) {
      return result;
    }

    DexFileLoaderErrorCode error_code;
    std::string error_msg;
    Analysis cumulative(&options);
    for (const std::string& filename : options.filenames_) {
      std::string content;
      // TODO: once added, use an API to android::base to read a std::vector<uint8_t>.
      if (!android::base::ReadFileToString(filename.c_str(), &content)) {
        LOG(ERROR) << "ReadFileToString failed for " + filename << std::endl;
        return kExitCodeFailedToOpenFile;
      }
      std::vector<std::unique_ptr<const DexFile>> dex_files;
      const DexFileLoader dex_file_loader;
      if (!dex_file_loader.OpenAll(reinterpret_cast<const uint8_t*>(content.data()),
                                   content.size(),
                                   filename.c_str(),
                                   options.run_dex_file_verifier_,
                                   options.verify_checksum_,
                                   &error_code,
                                   &error_msg,
                                   &dex_files)) {
        LOG(ERROR) << "OpenAll failed for " + filename << " with " << error_msg << std::endl;
        return kExitCodeFailedToOpenDex;
      }
      if (options.dump_per_input_dex_) {
        Analysis current(&options);
        if (!current.ProcessDexFiles(dex_files)) {
          LOG(ERROR) << "Failed to process " << filename << " with error " << error_msg;
          return kExitCodeFailedToProcessDex;
        }
        LOG(INFO) << "Analysis for " << filename << std::endl;
        current.Dump(LOG_STREAM(INFO));
      }
      cumulative.ProcessDexFiles(dex_files);
    }
    LOG(INFO) << "Cumulative analysis for " << cumulative.dex_count_ << " DEX files" << std::endl;
    cumulative.Dump(LOG_STREAM(INFO));
    return 0;
  }
};

}  // namespace dexanalyze
}  // namespace art

int main(int argc, char** argv) {
  return art::dexanalyze::DexAnalyze::Run(argc, argv);
}