/*
 * Copyright 2016 WebAssembly Community Group participants
 *
 * 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 <cstdio>
#include <cstdlib>
#include <cstring>

#include "src/common.h"
#include "src/option-parser.h"
#include "src/stream.h"
#include "src/binary-reader.h"
#include "src/binary-reader-objdump.h"

using namespace wabt;

static const char s_description[] =
R"(  Print information about the contents of wasm binaries.

examples:
  $ wasm-objdump test.wasm
)";

static ObjdumpOptions s_objdump_options;

static std::vector<const char*> s_infiles;

static std::unique_ptr<FileStream> s_log_stream;

static void ParseOptions(int argc, char** argv) {
  OptionParser parser("wasm-objdump", s_description);

  parser.AddOption('h', "headers", "Print headers",
                   []() { s_objdump_options.headers = true; });
  parser.AddOption(
      'j', "section", "SECTION", "Select just one section",
      [](const char* argument) { s_objdump_options.section_name = argument; });
  parser.AddOption('s', "full-contents", "Print raw section contents",
                   []() { s_objdump_options.raw = true; });
  parser.AddOption('d', "disassemble", "Disassemble function bodies",
                   []() { s_objdump_options.disassemble = true; });
  parser.AddOption("debug", "Print extra debug information", []() {
    s_objdump_options.debug = true;
    s_log_stream = FileStream::CreateStderr();
    s_objdump_options.log_stream = s_log_stream.get();
  });
  parser.AddOption('x', "details", "Show section details",
                   []() { s_objdump_options.details = true; });
  parser.AddOption('r', "reloc", "Show relocations inline with disassembly",
                   []() { s_objdump_options.relocs = true; });
  parser.AddOption(0, "section-offsets",
                   "Print section offsets instead of file offsets "
                   "in code disassembly",
                   []() { s_objdump_options.section_offsets = true; });
  parser.AddArgument(
      "filename", OptionParser::ArgumentCount::OneOrMore,
      [](const char* argument) { s_infiles.push_back(argument); });

  parser.Parse(argc, argv);
}

Result dump_file(const char* filename) {
  std::vector<uint8_t> file_data;
  CHECK_RESULT(ReadFile(filename, &file_data));

  uint8_t* data = file_data.data();
  size_t size = file_data.size();

  // Perform serveral passed over the binary in order to print out different
  // types of information.
  s_objdump_options.filename = filename;
  printf("\n");

  ObjdumpState state;

  Result result = Result::Ok;

  // Pass 0: Prepass
  s_objdump_options.mode = ObjdumpMode::Prepass;
  result |= ReadBinaryObjdump(data, size, &s_objdump_options, &state);
  s_objdump_options.log_stream = nullptr;

  // Pass 1: Print the section headers
  if (s_objdump_options.headers) {
    s_objdump_options.mode = ObjdumpMode::Headers;
    result |= ReadBinaryObjdump(data, size, &s_objdump_options, &state);
  }

  // Pass 2: Print extra information based on section type
  if (s_objdump_options.details) {
    s_objdump_options.mode = ObjdumpMode::Details;
    result |= ReadBinaryObjdump(data, size, &s_objdump_options, &state);
  }

  // Pass 3: Disassemble code section
  if (s_objdump_options.disassemble) {
    s_objdump_options.mode = ObjdumpMode::Disassemble;
    result |= ReadBinaryObjdump(data, size, &s_objdump_options, &state);
  }

  // Pass 4: Dump to raw contents of the sections
  if (s_objdump_options.raw) {
    s_objdump_options.mode = ObjdumpMode::RawData;
    result |= ReadBinaryObjdump(data, size, &s_objdump_options, &state);
  }

  return result;
}

int ProgramMain(int argc, char** argv) {
  InitStdio();

  ParseOptions(argc, argv);
  if (!s_objdump_options.headers && !s_objdump_options.details &&
      !s_objdump_options.disassemble && !s_objdump_options.raw) {
    fprintf(stderr, "At least one of the following switches must be given:\n");
    fprintf(stderr, " -d/--disassemble\n");
    fprintf(stderr, " -h/--headers\n");
    fprintf(stderr, " -x/--details\n");
    fprintf(stderr, " -s/--full-contents\n");
    return 1;
  }

  for (const char* filename: s_infiles) {
    if (Failed(dump_file(filename))) {
      return 1;
    }
  }

  return 0;
}

int main(int argc, char** argv) {
  WABT_TRY
  return ProgramMain(argc, argv);
  WABT_CATCH_BAD_ALLOC_AND_EXIT
}
