// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#include "verilator_memutil.h"

#include <array>
#include <cassert>
#include <cstring>
#include <getopt.h>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

namespace {
// An instruction to load the file at filepath to the memory called name. If
// name is the empty string then type must be kMemImageElf and this is an
// instruction to load an ELF file, picking memories by LMA.
struct LoadArg {
  std::string name;
  std::string filepath;
  MemImageType type;
};
}  // namespace

// Parse a meminit command-line argument. This should be of the form
// mem_area,file[,type]. Throw a std::runtime_error if something looks wrong.
static LoadArg ParseMemArg(std::string mem_argument) {
  std::array<std::string, 3> args;
  size_t pos = 0;
  size_t end_pos = 0;
  size_t i;

  for (i = 0; i < 3; ++i) {
    end_pos = mem_argument.find(",", pos);
    // Check for possible exit conditions
    if (pos == end_pos) {
      std::ostringstream oss;
      oss << "empty field in: `" << mem_argument << "'.";
      throw std::runtime_error(oss.str());
    }
    if (end_pos == std::string::npos) {
      args[i] = mem_argument.substr(pos);
      break;
    }
    args[i] = mem_argument.substr(pos, end_pos - pos);
    pos = end_pos + 1;
  }
  // mem_argument is not empty as getopt requires an argument,
  // but not a valid argument for memory initialization
  if (i == 0) {
    std::ostringstream oss;
    oss << "meminit must be in the format `name,file[,type]'. Got: `"
        << mem_argument << "'.";
    throw std::runtime_error(oss.str());
  }

  const char *str_type = (2 <= i) ? args[2].c_str() : nullptr;
  MemImageType type = DpiMemUtil::GetMemImageType(args[1], str_type);

  return {.name = args[0], .filepath = args[1], .type = type};
}

// Print a usage message to stdout
static void PrintHelp() {
  std::cout << "Simulation memory utilities:\n\n"
               "-r|--rominit=FILE\n"
               "  Initialize the ROM with FILE (elf/vmem)\n\n"
               "-m|--raminit=FILE\n"
               "  Initialize the RAM with FILE (elf/vmem)\n\n"
               "-f|--flashinit=FILE\n"
               "  Initialize the FLASH with FILE (elf/vmem)\n\n"
               "-l|--meminit=NAME,FILE[,TYPE]\n"
               "  Initialize memory region NAME with FILE [of TYPE]\n"
               "  TYPE is either 'elf' or 'vmem'\n\n"
               "-E|--load-elf=FILE\n"
               "  Load ELF file, using segment LMAs to pick memory regions\n\n"
               "-l list|--meminit=list\n"
               "  Print registered memory regions\n\n"
               "--verbose-mem-load\n"
               "  Print a message for each memory load\n\n"
               "-h|--help\n"
               "  Show help\n\n";
}

VerilatorMemUtil::VerilatorMemUtil() : allocation_(new DpiMemUtil()) {
  mem_util_ = allocation_.get();
}

VerilatorMemUtil::VerilatorMemUtil(DpiMemUtil *mem_util) : mem_util_(mem_util) {
  assert(mem_util);
}

bool VerilatorMemUtil::ParseCLIArguments(int argc, char **argv,
                                         bool &exit_app) {
  const struct option long_options[] = {
      {"rominit", required_argument, nullptr, 'r'},
      {"raminit", required_argument, nullptr, 'm'},
      {"flashinit", required_argument, nullptr, 'f'},
      {"otpinit", required_argument, nullptr, 'o'},
      {"meminit", required_argument, nullptr, 'l'},
      {"verbose-mem-load", no_argument, nullptr, 'V'},
      {"load-elf", required_argument, nullptr, 'E'},
      {"help", no_argument, nullptr, 'h'},
      {nullptr, no_argument, nullptr, 0}};

  std::vector<LoadArg> load_args;
  bool verbose = false;

  // Reset the command parsing index in-case other utils have already parsed
  // some arguments
  optind = 1;
  while (1) {
    int c = getopt_long(argc, argv, "-:r:m:f:l:E:h", long_options, nullptr);
    if (c == -1) {
      break;
    }

    // Disable error reporting by getopt
    opterr = 0;

    switch (c) {
      case 0:
      case 1:
        break;
      case 'r':
        load_args.push_back(
            {.name = "rom", .filepath = optarg, .type = kMemImageUnknown});
        break;
      case 'm':
        load_args.push_back(
            {.name = "ram", .filepath = optarg, .type = kMemImageUnknown});
        break;
      case 'f':
        load_args.push_back(
            {.name = "flash", .filepath = optarg, .type = kMemImageUnknown});
        break;
      case 'o':
        load_args.push_back(
            {.name = "otp", .filepath = optarg, .type = kMemImageUnknown});
        break;
      case 'l':
        if (strcasecmp(optarg, "list") == 0) {
          mem_util_->PrintMemRegions();
          exit_app = true;
          return true;
        }

        // --meminit / -l
        try {
          load_args.emplace_back(ParseMemArg(optarg));
        } catch (const std::runtime_error &err) {
          std::cerr << "ERROR: " << err.what() << std::endl;
          return false;
        }
        break;
      case 'V':
        verbose = true;
        break;
      case 'E':
        load_args.push_back(
            {.name = "", .filepath = optarg, .type = kMemImageElf});
        break;
      case 'h':
        PrintHelp();
        return true;
      case ':':  // missing argument
        std::cerr << "ERROR: Missing argument." << std::endl << std::endl;
        return false;
      case '?':
      default:;
        // Ignore unrecognized options since they might be consumed by
        // other utils
    }
  }

  for (const LoadArg &arg : load_args) {
    try {
      if (!arg.name.empty()) {
        mem_util_->LoadFileToNamedMem(verbose, arg.name, arg.filepath,
                                      arg.type);
      } else {
        assert(arg.type == kMemImageElf);
        mem_util_->LoadElfToMemories(verbose, arg.filepath);
      }
    } catch (const std::exception &err) {
      std::cerr << "ERROR: " << err.what() << std::endl;
      return false;
    }
  }

  return true;
}
