//===-- main.cppp ---------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "CoreSpec.h"
#include "LCNoteWriter.h"
#include "MemoryWriter.h"
#include "ThreadWriter.h"
#include "Utility.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Support/CommandLine.h"
#include <stdio.h>
#include <string>
#include <sys/stat.h>

std::vector<std::string> get_fields_from_delimited_string(std::string str,
                                                          const char delim) {
  std::vector<std::string> result;
  std::string::size_type prev = std::string::npos;
  std::string::size_type next = str.find(delim);
  if (str.empty()) {
    return result;
  }
  if (next == std::string::npos) {
    result.push_back(str);
  } else {
    result.push_back(std::string(str, 0, next));
    prev = next;
    while ((next = str.find(delim, prev + 1)) != std::string::npos) {
      result.push_back(std::string(str, prev + 1, next - prev - 1));
      prev = next;
    }
    result.push_back(std::string(str, prev + 1));
  }
  return result;
}

llvm::cl::opt<std::string> InputFilename("i", llvm::cl::Required,
                                         llvm::cl::desc("input yaml filename"),
                                         llvm::cl::value_desc("input"));
llvm::cl::opt<std::string>
    OutputFilename("o", llvm::cl::Required,
                   llvm::cl::desc("output core filenames"),
                   llvm::cl::value_desc("output"));
llvm::cl::list<std::string>
    UUIDs("u", llvm::cl::desc("uuid of binary loaded at slide 0"),
          llvm::cl::value_desc("uuid"));
llvm::cl::list<std::string>
    UUIDAndVAs("L", llvm::cl::desc("UUID,virtual-address-loaded-at"),
               llvm::cl::value_desc("--uuid-and-load-addr"));
llvm::cl::opt<int>
    AddressableBitsOverride("A",
                            llvm::cl::desc("number of bits used in addressing"),
                            llvm::cl::value_desc("--address-bits"));

int main(int argc, char **argv) {
  llvm::cl::ParseCommandLineOptions(argc, argv);

  if (InputFilename.empty() || OutputFilename.empty()) {
    fprintf(stderr, "Missing input or outpur file.\n");
    exit(1);
  }

  struct stat sb;

  if (stat(InputFilename.c_str(), &sb) == -1) {
    fprintf(stderr, "Unable to stat %s, exiting\n", InputFilename.c_str());
    exit(1);
  }

  FILE *input = fopen(InputFilename.c_str(), "r");
  if (!input) {
    fprintf(stderr, "Unable to open %s, exiting\n", InputFilename.c_str());
    exit(1);
  }
  auto file_corespec = std::make_unique<char[]>(sb.st_size);
  if (fread(file_corespec.get(), sb.st_size, 1, input) != 1) {
    fprintf(stderr, "Unable to read all of %s, exiting\n",
            InputFilename.c_str());
    exit(1);
  }
  CoreSpec spec = from_yaml(file_corespec.get(), sb.st_size);
  fclose(input);

  for (const std::string &uuid : UUIDs) {
    Binary binary;
    binary.uuid = uuid;
    binary.value = 0;
    binary.value_is_slide = true;
    spec.binaries.push_back(binary);
  }

  for (const std::string &uuid_and_va : UUIDAndVAs) {
    std::vector<std::string> parts =
        get_fields_from_delimited_string(uuid_and_va, ',');

    std::string uuid = parts[0];
    uint64_t va = std::strtoull(parts[1].c_str(), nullptr, 16);
    Binary binary;
    binary.uuid = uuid;
    binary.value = va;
    binary.value_is_slide = false;
    spec.binaries.push_back(binary);
  }

  if (AddressableBitsOverride) {
    AddressableBits bits;
    bits.lowmem_bits = bits.highmem_bits = AddressableBitsOverride;
    spec.addressable_bits = bits;
  }

  // An array of load commands
  std::vector<std::vector<uint8_t>> load_commands;

  // An array of corefile contents (memory regions)
  std::vector<uint8_t> payload;

  // First add all the load commands / payload so we can figure out how large
  // the load commands will be.

  add_lc_threads(spec, load_commands);
  for (size_t i = 0; i < spec.memory_regions.size(); i++) {
    std::vector<uint8_t> segment_command_bytes;
    create_lc_segment_cmd(spec, segment_command_bytes, spec.memory_regions[i],
                          0);
    load_commands.push_back(segment_command_bytes);
  }

  if (spec.binaries.size() > 0)
    for (const Binary &binary : spec.binaries) {
      std::vector<uint8_t> segment_command_bytes;
      std::vector<uint8_t> payload_bytes;
      create_lc_note_binary_load_cmd(spec, segment_command_bytes, binary,
                                     payload_bytes, 0);
      load_commands.push_back(segment_command_bytes);
    }
  if (spec.addressable_bits) {
    std::vector<uint8_t> segment_command_bytes;
    std::vector<uint8_t> payload_bytes;
    create_lc_note_addressable_bits(spec, segment_command_bytes,
                                    *spec.addressable_bits, payload_bytes, 0);
    load_commands.push_back(segment_command_bytes);
  }

  off_t size_of_load_commands = 0;
  for (const auto &lc : load_commands)
    size_of_load_commands += lc.size();

  off_t header_and_load_cmd_room =
      sizeof(llvm::MachO::mach_header_64) + size_of_load_commands;
  off_t initial_payload_fileoff = header_and_load_cmd_room;
  initial_payload_fileoff = (initial_payload_fileoff + 4096 - 1) & ~(4096 - 1);
  off_t payload_fileoff = initial_payload_fileoff;

  // Erase the load commands / payload now that we know how much space is
  // needed, redo it with real values.
  load_commands.clear();
  payload.clear();

  add_lc_threads(spec, load_commands);
  for (size_t i = 0; i < spec.memory_regions.size(); i++) {
    std::vector<uint8_t> segment_command_bytes;
    create_lc_segment_cmd(spec, segment_command_bytes, spec.memory_regions[i],
                          payload_fileoff);
    load_commands.push_back(segment_command_bytes);
    payload_fileoff += spec.memory_regions[i].size;
    payload_fileoff = (payload_fileoff + 4096 - 1) & ~(4096 - 1);
  }

  off_t payload_fileoff_before_lcnotes = payload_fileoff;
  std::vector<uint8_t> lc_note_payload_bytes;
  if (spec.binaries.size() > 0)
    for (const Binary &binary : spec.binaries) {
      std::vector<uint8_t> segment_command_bytes;
      std::vector<uint8_t> payload_bytes;
      create_lc_note_binary_load_cmd(spec, segment_command_bytes, binary,
                                     lc_note_payload_bytes, payload_fileoff);
      payload_fileoff =
          payload_fileoff_before_lcnotes + lc_note_payload_bytes.size();
      load_commands.push_back(segment_command_bytes);
    }
  if (spec.addressable_bits) {
    std::vector<uint8_t> segment_command_bytes;
    std::vector<uint8_t> payload_bytes;
    create_lc_note_addressable_bits(spec, segment_command_bytes,
                                    *spec.addressable_bits,
                                    lc_note_payload_bytes, payload_fileoff);
    payload_fileoff =
        payload_fileoff_before_lcnotes + lc_note_payload_bytes.size();
    load_commands.push_back(segment_command_bytes);
  }

  // Realign our payload offset if we added any LC_NOTEs.
  if (lc_note_payload_bytes.size() > 0)
    payload_fileoff = (payload_fileoff + 4096 - 1) & ~(4096 - 1);

  FILE *f = fopen(OutputFilename.c_str(), "wb");
  if (f == nullptr) {
    fprintf(stderr, "Unable to open file %s for writing\n",
            OutputFilename.c_str());
    exit(1);
  }

  std::vector<uint8_t> mh;
  // Write the fields of a mach_header_64 struct
  if (spec.wordsize == 8)
    add_uint32(mh, llvm::MachO::MH_MAGIC_64); // magic
  else
    add_uint32(mh, llvm::MachO::MH_MAGIC); // magic
  add_uint32(mh, spec.cputype);            // cputype
  add_uint32(mh, spec.cpusubtype);         // cpusubtype
  add_uint32(mh, llvm::MachO::MH_CORE);    // filetype
  add_uint32(mh, load_commands.size());    // ncmds
  add_uint32(mh, size_of_load_commands);   // sizeofcmds
  add_uint32(mh, 0);                       // flags
  if (spec.wordsize == 8)
    add_uint32(mh, 0); // reserved

  fwrite(mh.data(), mh.size(), 1, f);

  for (const auto &lc : load_commands)
    fwrite(lc.data(), lc.size(), 1, f);

  // Reset the payload offset back to the first one.
  payload_fileoff = initial_payload_fileoff;
  if (spec.memory_regions.size() > 0) {
    for (size_t i = 0; i < spec.memory_regions.size(); i++) {
      std::vector<uint8_t> bytes;
      create_memory_bytes(spec, spec.memory_regions[i], bytes);
      fseek(f, payload_fileoff, SEEK_SET);
      fwrite(bytes.data(), bytes.size(), 1, f);

      payload_fileoff += bytes.size();
      payload_fileoff = (payload_fileoff + 4096 - 1) & ~(4096 - 1);
    }
  }

  if (lc_note_payload_bytes.size() > 0) {
    fseek(f, payload_fileoff, SEEK_SET);
    fwrite(lc_note_payload_bytes.data(), lc_note_payload_bytes.size(), 1, f);
    payload_fileoff += lc_note_payload_bytes.size();
    payload_fileoff = (payload_fileoff + 4096 - 1) & ~(4096 - 1);
  }

  fclose(f);
}
