/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 */

#include "./data-printer.hpp"

#include <cstdint>
#include <iomanip>
#include <iostream>
#include <sstream>

namespace sh4lt::utils {

template <typename T>
std::string print_int_vals(const void* data,
                           const unsigned int max_val_displayed,
                           const std::size_t size,
                           const std::string& separator,
                           const std::size_t min_width) {
  if (size == 0) {
    return "";
  }
  std::ostringstream res;
  auto vals = static_cast<const T*>(data);
  auto val_index = 0u;
  const auto last_val = size / sizeof(T) - 1;
  const auto max_val = max_val_displayed < last_val + 1 ? max_val_displayed : last_val + 1;
  while ((max_val_displayed == 0 || val_index < max_val) && last_val < (size / sizeof(T))) {
    if (min_width != 0) {
      res << std::setw(min_width);
    }
    res << +vals[val_index];  // '+' is required for int8_t to be printed as a number
    if (val_index != last_val) {
      res << separator;
    }
    ++val_index;
  }
  if (val_index <= last_val) res << "...";
  return res.str();
}

template <typename T>
std::string print_float_vals(const void* data,
                             const unsigned int max_val_displayed,
                             const std::size_t size,
                             const std::string& separator,
                             const std::size_t precision,
                             const std::size_t min_width) {
  if (size == 0) {
    return "";
  }
  std::ostringstream res;
  auto vals = static_cast<const T*>(data);
  auto val_index = 0u;
  const auto last_val = size / sizeof(T) - 1;
  const auto max_val = max_val_displayed < last_val + 1 ? max_val_displayed : last_val + 1;
  while ((max_val_displayed == 0 || val_index < max_val) && val_index <= last_val) {
    if (min_width != 0) {
      res << std::setw(min_width);
    }
    res << std::fixed << std::setprecision(precision) << vals[val_index];
    if (val_index != last_val) {
      res << separator;
    }
    ++val_index;
  }
  if (val_index <= last_val) res << "...";
  return res.str();
}

std::string print_vals(const void* data,
                       unsigned int max_val_displayed,
                       const std::size_t size,
                       const std::string& separator) {
  if (size == 0) {
    return "";
  }
  std::ostringstream res;
  auto vals = static_cast<const char*>(data);
  auto val_index = 0u;
  const auto last_val = size / sizeof(char) - 1;
  const auto max_val = max_val_displayed < last_val + 1 ? max_val_displayed : last_val + 1;
  while ((max_val_displayed == 0 || val_index < max_val) && val_index * sizeof(char) < size) {
    res << vals[val_index];
    if (val_index != last_val) {
      res << separator;
    }
    ++val_index;
  }
  if (val_index <= last_val) res << "...";
  return res.str();
}

std::string print_hex(const void* data,
                      unsigned int max_val_displayed,
                      const std::size_t size,
                      const std::string& separator) {
  if (size == 0) {
    return "";
  }
  std::ostringstream res;
  res << std::hex << std::uppercase;
  auto vals = static_cast<const char*>(data);
  auto val_index = 0u;
  const auto last_val = size / sizeof(char) - 1;
  const auto max_val = max_val_displayed < last_val + 1 ? max_val_displayed : last_val + 1;
  while ((max_val_displayed == 0 || val_index < max_val) && val_index * sizeof(char) < size) {
    res << std::setfill('0') << std::setw(2) << (0xff & static_cast<unsigned int>(vals[val_index]));
    if (val_index != last_val) {
      res << separator;
    }
    ++val_index;
  }
  if (val_index <= last_val) res << "...";
  return res.str();
}

std::optional<data_printer_t> make_data_printer_fun(std::string format,
                                                    std::string separator,
                                                    size_t precision,
                                                    size_t min_width) {
  if (format == "x") {
    return [=](const void* data, std::size_t size, std::size_t num_val_to_print) -> std::string {
      return print_hex(data, num_val_to_print, size, separator);
    };
  } else if (format == "f") {
    return [=](const void* data, std::size_t size, std::size_t num_val_to_print) -> std::string {
      return print_float_vals<float>(data, num_val_to_print, size, separator, precision, min_width);
    };
  } else if (format == "i16") {
    return [=](const void* data, std::size_t size, std::size_t num_val_to_print) -> std::string {
      return print_int_vals<int16_t>(data, num_val_to_print, size, separator, min_width);
    };
  } else if (format == "c") {
    return [=](const void* data, std::size_t size, std::size_t num_val_to_print) -> std::string {
      return print_vals(data, num_val_to_print, size, separator);
    };
  } else if (format == "d") {
    return [=](const void* data, std::size_t size, std::size_t num_val_to_print) -> std::string {
      return print_float_vals<double>(
          data, num_val_to_print, size, separator, precision, min_width);
    };
  } else if (format == "i8") {
    return [=](const void* data, std::size_t size, std::size_t num_val_to_print) -> std::string {
      return print_int_vals<int8_t>(data, num_val_to_print, size, separator, min_width);
    };
  } else if (format == "i32") {
    return [=](const void* data, std::size_t size, std::size_t num_val_to_print) -> std::string {
      return print_int_vals<int32_t>(data, num_val_to_print, size, separator, min_width);
    };
  } else if (format == "i64") {
    return [=](const void* data, std::size_t size, std::size_t num_val_to_print) -> std::string {
      return print_int_vals<int64_t>(data, num_val_to_print, size, separator, min_width);
    };
  } else if (format == "ui8") {
    return [=](const void* data, std::size_t size, std::size_t num_val_to_print) -> std::string {
      return print_int_vals<uint8_t>(data, num_val_to_print, size, separator, min_width);
    };
  } else if (format == "ui16") {
    return [=](const void* data, std::size_t size, std::size_t num_val_to_print) -> std::string {
      return print_int_vals<uint16_t>(data, num_val_to_print, size, separator, min_width);
    };
  } else if (format == "ui32") {
    return [=](const void* data, std::size_t size, std::size_t num_val_to_print) -> std::string {
      return print_int_vals<uint32_t>(data, num_val_to_print, size, separator, min_width);
    };
  } else if (format == "ui64") {
    return [=](const void* data, std::size_t size, std::size_t num_val_to_print) -> std::string {
      return print_int_vals<uint64_t>(data, num_val_to_print, size, separator, min_width);
    };
  }
  return {};  // format not found
}
}  // namespace sh4lt::utils
