// Copyright(C) 1999-2024 National Technology & Engineering Solutions
// of Sandia, LLC (NTESS).  Under the terms of Contract DE-NA0003525 with
// NTESS, the U.S. Government retains certain rights in this software.
//
// See packages/seacas/LICENSE for details

#include "Ioss_CodeTypes.h"
#include <cassert>
#include <cctype>
#include <cstdint>
#include <cstring>
#include <ctime>
#include "vtk_fmt.h"
#include VTK_FMT(fmt/chrono.h)
#include VTK_FMT(fmt/core.h)
#include VTK_FMT(fmt/format.h)
#include VTK_FMT(fmt/ostream.h)
#include VTK_FMT(fmt/ranges.h)
#include <fstream>
#include <ostream>
#include <sstream>
#include <string>
#include <tokenize.h>
#include <vector>

#if !defined(__IOSS_WINDOWS__)
#include <sys/ioctl.h>
#include <sys/utsname.h>
#endif

#ifndef _POSIX_SOURCE
#define _POSIX_SOURCE
#endif
#include <cstdio>

#include "Ioss_CompositeVariableType.h"
#include "Ioss_DatabaseIO.h"
#include "Ioss_ElementBlock.h"
#include "Ioss_ElementTopology.h"
#include "Ioss_EntityType.h"
#include "Ioss_Field.h"
#include "Ioss_FileInfo.h"
#include "Ioss_GroupingEntity.h"
#include "Ioss_IOFactory.h"
#include "Ioss_NodeBlock.h"
#include "Ioss_Property.h"
#include "Ioss_PropertyManager.h"
#include "Ioss_Region.h"
#include "Ioss_SideBlock.h"
#include "Ioss_State.h"
#include "Ioss_Utils.h"
#include "Ioss_VariableType.h"

#if defined(__IOSS_WINDOWS__)
#include <io.h>

#define isatty _isatty
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <unistd.h>
#endif

std::ostream *Ioss::Utils::m_warningStream  = &std::cerr;
std::ostream *Ioss::Utils::m_debugStream    = &std::cerr;
std::ostream *Ioss::Utils::m_outputStream   = &std::cerr;
std::string   Ioss::Utils::m_preWarningText = "\nIOSS WARNING: ";

namespace {
  auto initial_time = std::chrono::steady_clock::now();

  ////////////////////////////////////////////////////////////////////////
  bool is_separator(const char separator, const char value) { return separator == value; }

  size_t match(const char *name1, const char *name2)
  {
    auto l1  = std::strlen(name1);
    auto l2  = std::strlen(name2);
    auto len = l1 < l2 ? l1 : l2;
    for (size_t i = 0; i < len; i++) {
      if (name1[i] != name2[i]) {
        while (i > 0 && (isdigit(name1[i - 1]) != 0) && (isdigit(name2[i - 1]) != 0)) {
          i--;
          // Back up to first non-digit so to handle "evar0000, evar0001, ..., evar 1123"
        }
        return i;
      }
    }
    return len;
  }

  size_t match(const std::string &name1, const std::string &name2)
  {
    return match(name1.c_str(), name2.c_str());
  }

  // Split 'str' into 'tokens' based on the 'separator' character.
  // If 'str' starts with 1 or more 'separator', they are part of the
  // first token and not used for splitting.  If there are multiple
  // 'separator' characters in a row, then the first is used to split
  // and the subsequent 'separator' characters are put as leading
  // characters of the next token.
  // `__this___is_a_string__for_tokens` will split to 6 tokens:
  // `__this` `__is` `a` `string` `_for` `tokens`
  Ioss::NameList field_tokenize(const std::string &str, const char separator)
  {
    std::string curr_token;
    // Skip leading separators...
    size_t i = 0;
    while (i < str.length() && is_separator(separator, str[i])) {
      curr_token += str[i++];
    }
    Ioss::NameList tokens;
    for (; i < str.length(); ++i) {
      char curr_char = str[i];

      // determine if current character is a separator
      bool is_sep = is_separator(separator, curr_char);
      if (is_sep && !curr_token.empty()) {
        // we just completed a token
        tokens.push_back(curr_token);
        curr_token.clear();
        while (i++ < str.length() && is_separator(separator, str[i])) {
          curr_token += str[i];
        }
        i--;
      }
      else if (!is_sep) {
        curr_token += curr_char;
      }
    }
    if (!curr_token.empty()) {
      tokens.push_back(curr_token);
    }
    return tokens;
  }
} // namespace

void Ioss::Utils::set_all_streams(std::ostream &out_stream)
{
  m_outputStream  = &out_stream;
  m_debugStream   = &out_stream;
  m_warningStream = &out_stream;
}

void Ioss::Utils::set_output_stream(std::ostream &out_stream) { m_outputStream = &out_stream; }

void Ioss::Utils::set_debug_stream(std::ostream &out_stream) { m_debugStream = &out_stream; }

void Ioss::Utils::set_warning_stream(std::ostream &out_stream) { m_warningStream = &out_stream; }

std::ostream &Ioss::Utils::get_output_stream() { return *m_outputStream; }

std::ostream &Ioss::Utils::get_warning_stream() { return *m_warningStream; }

std::ostream &Ioss::Utils::get_debug_stream() { return *m_debugStream; }

void Ioss::Utils::time_and_date(char *time_string, char *date_string, size_t length)
{
  std::time_t t    = std::time(nullptr);
  std::string time = fmt::format("{:%H:%M:%S}", fmt::localtime(t));
  std::string date;
  if (length >= 10) {
    date = fmt::format("{:%Y/%m/%d}", fmt::localtime(t));
  }
  else {
    date = fmt::format("{:%y/%m/%d}", fmt::localtime(t));
  }
  copy_string(time_string, time, 9);
  copy_string(date_string, date, length + 1);
}

void Ioss::Utils::check_non_null(void *ptr, const char *type, const std::string &name,
                                 const std::string &func)
{
  if (ptr == nullptr) {
    std::ostringstream errmsg;
    fmt::print(errmsg,
               "INTERNAL ERROR: Could not find {} '{}'. Something is wrong in {}. Please report.\n",
               type, name, func);
    IOSS_ERROR(errmsg);
  }
}

std::string Ioss::Utils::decode_filename(const std::string &filename, int processor,
                                         int num_processors)
{
  // Current format for per-processor file names is:
  // PREFIX/basename.num_proc.cur_proc
  // the 'cur_proc' field is padded to be the same width as
  // the 'num_proc' field
  // Examples: basename.8.1, basename.64.03, basename.128.001

  // Create a std::string containing the total number of processors
  if (num_processors > 1) {
    size_t proc_width = number_width(num_processors);

    std::string decoded_filename =
        fmt::format("{}.{}.{:0{}}", filename, num_processors, processor, proc_width);
    return decoded_filename;
  }
  return filename;
}

std::string Ioss::Utils::get_trailing_digits(const std::string &name)
{
  size_t digits = name.find_last_not_of("0123456789");
  if (digits != std::string::npos) {
    return name.substr(digits + 1);
  }
  return std::string{};
}

int Ioss::Utils::get_number(const std::string &suffix)
{
  int  N       = 0;
  bool all_dig = suffix.find_first_not_of("0123456789") == std::string::npos;
  if (all_dig) {
    N = std::stoi(suffix);
  }
  return N;
}

int Ioss::Utils::extract_id(const std::string &name_id)
{
  int id = 0;

  auto tokens = Ioss::tokenize(name_id, "_");
  if (tokens.size() > 1) {
    // Check whether last token is an integer...
    std::string str_id = tokens.back();
    id                 = get_number(str_id);
  }
  return id;
}

std::string Ioss::Utils::format_id_list(const std::vector<size_t> &ids, const std::string &rng_sep,
                                        const std::string &seq_sep)
{
  // Based on function from cubit (but I wrote original cubit version long time ago... ;-)
  if (ids.empty()) {
    return "";
  }

  // PRECONDITION: `ids` is monotonically increasing -- will throw IOSS_ERROR if violated.
  if (!std::is_sorted(ids.begin(), ids.end(), [](size_t a, size_t b) { return a <= b; })) {
    std::ostringstream errmsg;
    fmt::print(errmsg,
               "INTERNAL ERROR: ({}) The `ids` vector is not in monotonically increasing order as "
               "required.\n",
               __func__);
    IOSS_ERROR(errmsg);
  }

  size_t             num = 0;
  std::ostringstream ret_str;
  while (num < ids.size()) {
    fmt::print(ret_str, "{}{}", num == 0 ? "" : seq_sep, ids[num]);
    size_t begin    = ids[num]; // first id in range of 1 or more ids
    size_t previous = ids[num]; // last id in range of 1 or more ids
    // Gather a range or single value... (begin .. previous)
    while (previous == ids[num] && ++num < ids.size() && ids[num] == previous + 1) {
      previous++;
    }

    if (begin != previous) {
      fmt::print(ret_str, "{}{}", previous == begin + 1 ? seq_sep : rng_sep, previous);
    }
  }
  return ret_str.str();
}

std::string Ioss::Utils::encode_entity_name(const std::string &entity_type, int64_t id)
{
  // ExodusII stores block, nodeset, and sideset ids as integers
  // Sierra   stores these as std::strings. The string is created by
  // concatenating the type, the character '_' and the id.

  return fmt::format("{}_{}", entity_type, id);
}

std::string Ioss::Utils::fixup_type(const std::string &base, int nodes_per_element, int spatial)
{
  std::string type = base;
  Ioss::Utils::fixup_name(type); // Convert to lowercase; replace spaces with '_'

  // Fixup an exodusII kluge/ambiguity.
  // The element block type does not fully define the element. For
  // example, a block of type 'triangle' may have either 3 or 6
  // nodes.  To fix this, check the block type name and see if it
  // ends with a number.  If it does, assume it is OK; if not, append
  // the 'nodes_per_element'.
  if (type.empty() || isdigit(*(type.rbegin())) == 0) {
    if (nodes_per_element > 1) {
      type += std::to_string(nodes_per_element);
    }
  }

  // Fixup an exodusII kludge.  For triangular elements, the same
  // name is used for 2D elements and 3D shell elements.  Convert
  // to unambiguous names for the IO Subsystem.  The 2D name
  // stays the same, the 3D name becomes 'trishell#'
  if (spatial == 3) {
    if (type == "triangle3") {
      type = "trishell3";
    }
    else if (type == "triangle4") {
      type = "trishell4";
    }
    else if (type == "triangle6") {
      type = "trishell6";
    }
    else if (type == "tri3") {
      type = "trishell3";
    }
    else if (type == "tri4") {
      type = "trishell4";
    }
    else if (type == "tri6") {
      type = "trishell6";
    }
  }

  if (spatial == 2) {
    if (type == "shell2") {
      type = "shellline2d2";
    }
    else if (type == "rod2" || type == "bar2" || type == "truss2") {
      type = "rod2d2";
    }
    else if (type == "shell3") {
      type = "shellline2d3";
    }
    else if (type == "bar3" || type == "rod3" || type == "truss3") {
      type = "rod2d3";
    }
    else if (type == "bar4" || type == "rod4" || type == "truss4") {
      type = "rod2d4";
    }
  }

  if (Ioss::Utils::substr_equal("super", type)) {
    // A super element can have a varying number of nodes.  Create
    // an IO element type for this super element just so the IO
    // system can read a mesh containing super elements.  This
    // allows the "omit volume" command to be used in the Sierra
    // applications to skip creating a corresponding element block
    // in the application.
    type = "super" + std::to_string(nodes_per_element);
  }
  return type;
}

std::string Ioss::Utils::local_filename(const std::string &relative_filename,
                                        const std::string &type,
                                        const std::string &working_directory)
{
  if (relative_filename[0] == '/' || type == "generated" || working_directory.empty()) {
    return relative_filename;
  }
  std::string filename = working_directory;
  filename += relative_filename;
  return filename;
}

int Ioss::Utils::field_warning(const Ioss::GroupingEntity *ge, const Ioss::Field &field,
                               const std::string &inout)
{
  if (field.get_name() != "ids") {
    fmt::print(Ioss::WarnOut(), "{} '{}'. Unknown {} field '{}'\n", ge->type_string(), ge->name(),
               inout, field.get_name());
  }
  return -4;
}

namespace {
  const Ioss::VariableType *match_composite_field(Ioss::NameList  &names,
                                                  Ioss::IntVector &which_names,
                                                  const char       suffix_separator)
  {
    // ASSUME: Fields are in order...
    // The field we are trying to match will be a composite field of
    // type base_x_1, base_y_1, base_z_1, ...., base_y_3, base_z_3.
    // The composite field type currently always has a numeric Real[N]
    // type field as the last suffix and the other field as the first
    // suffix.
    // If we take the last suffix of the last name, it should give us
    // the 'N' in the Real[N] field.  Dividing 'which_names.size()' by
    // 'N' will give the number of components in the inner field.

    char suffix[2] = {suffix_separator, '\0'};

    auto tokens = Ioss::tokenize(names[which_names.back()], suffix);

    if (tokens.size() <= 2) {
      return nullptr;
    }

    assert(tokens.size() > 2);

    // Check that suffix is a number -- all digits
    int N = Ioss::Utils::get_number(tokens.back());

    if (N == 0) {
      return nullptr;
    }

    if (which_names.size() % N != 0) {
      return nullptr;
    }

    size_t inner_token = tokens.size() - 2;
    size_t inner_comp  = which_names.size() / N;

    // Gather the first 'inner_ccomp' inner field suffices...
    std::vector<Ioss::Suffix> suffices;
    for (size_t i = 0; i < inner_comp; i++) {
      auto ltokens = Ioss::tokenize(std::string(names[which_names[i]]), suffix);
      // The second-last token is the suffix for this component...
      Ioss::Suffix tmp(ltokens[inner_token]);
      suffices.push_back(tmp);
    }

    // check that the suffices on the next copies of the inner field
    // match the first copy...
    size_t j = inner_comp;
    for (int copy = 1; copy < N; copy++) {
      for (size_t i = 0; i < inner_comp; i++) {
        auto ltokens = Ioss::tokenize(std::string(names[which_names[j++]]), suffix);
        // The second-last token is the suffix for this component...
        if (suffices[i] != ltokens[inner_token]) {
          return nullptr;
        }
      }
    }

    // All 'N' copies of the inner field match, now see the
    // suffices actually defines a field...
    const Ioss::VariableType *type = Ioss::VariableType::factory(suffices);
    if (type != nullptr) {
      type = Ioss::VariableType::factory(type->name(), N);
    }
    return type;
  }

  const Ioss::VariableType *match_single_field(Ioss::NameList &names, Ioss::IntVector &which_names,
                                               const char suffix_separator,
                                               bool       ignore_realn_fields)
  {
    // Strip off the suffix from each name indexed in 'which_names'
    // and see if it defines a valid type...
    std::vector<Ioss::Suffix> suffices;

    char suffix[2] = {suffix_separator, '\0'};

    for (int which_name : which_names) {
      auto   tokens     = Ioss::tokenize(names[which_name], suffix);
      size_t num_tokens = tokens.size();

      // The last token is the suffix for this component...
      Ioss::Suffix tmp(tokens[num_tokens - 1]);
      suffices.push_back(tmp);
    }
    const Ioss::VariableType *type = Ioss::VariableType::factory(suffices, ignore_realn_fields);
    return type;
  }

  Ioss::Field get_next_field(Ioss::NameList &names, size_t count, Ioss::Field::RoleType fld_role,
                             const char suffix_separator, const int *truth_table,
                             bool ignore_realn_fields)
  {
    // NOTE: 'names' are all lowercase at this point.

    // Assumption 1: To convert to a non-SCALAR type, the variable name
    // must have an field_suffix_sep in the name separating the suffixes from
    // the main name.

    // Find first unused name (used names are empty())
    size_t index       = 0;
    bool   found_valid = false;
    for (index = 0; index < names.size(); index++) {
      assert(truth_table == nullptr || truth_table[index] == 1 || truth_table[index] == 0);
      if ((truth_table == nullptr || truth_table[index] == 1) && !names[index].empty()) {
        found_valid = true;
        break;
      }
    }

    if (!found_valid) {
      // Return an invalid field...
      return {"", Ioss::Field::INVALID, IOSS_SCALAR(), fld_role, 1};
    }

    // At this point, name[index] should be a valid potential field
    // name and all names[i] with i < index are either already used or
    // not valid for this grouping entity (truth_table entry == 0).
    assert(index < names.size() && !names[index].empty() &&
           (truth_table == nullptr || truth_table[index] == 1));
    const auto &name        = names[index];
    auto        name_length = name.length();

    // Split the name up into tokens separated by the
    // 'suffix_separator'.  Note that the basename itself could
    // contain a suffix_separator (back_stress_xx or
    // back_stress_xx_01). Need to ignore embedded separators
    // (back_stress) and also recognize composite variable types
    // (back_stress_xx_01). At the current time, a composite variable
    // type can only contain two non-composite variable types, so we
    // only need to look to be concerned with the last 1 or 2 tokens...
    auto   tokens     = field_tokenize(name, suffix_separator);
    size_t num_tokens = tokens.size();

    // Check that tokenizer did not return empty tokens...
    bool invalid = tokens[0].empty() || tokens[num_tokens - 1].empty();
    if (num_tokens == 1 || invalid) {
      // It is not a (Sierra-generated) name for a non-SCALAR variable
      // Return a SCALAR field
      Ioss::Field field(name, Ioss::Field::REAL, IOSS_SCALAR(), fld_role, count);
      field.set_index(index);
      names[index] = "";
      return field;
    }

    // KNOW: num_tokens > 1 at this point.  Possible that we still
    // just have a scalar with one or more embedded separator characters...
    int suffix_size = 1;
    if (num_tokens > 2) {
      suffix_size = 2;
    }

    // If num_tokens > 2, then we can potentially have a composite
    // variable type which would have a double suffix (_xx_01).

    // Gather all names which match in the first
    // (num_tokens-suffix_size) tokens and see if their suffices form
    // a valid variable type...
    while (suffix_size > 0) {
      for (int i = 0; i <= 1; i++) {
        bool            same_length = (i == 0);
        Ioss::IntVector which_names; // Contains index of names that potentially match as components
                                     // of a higher-order type.
        std::string base_name = tokens[0];
        for (size_t it = 1; it < num_tokens - suffix_size; it++) {
          base_name += suffix_separator;
          base_name += tokens[it];
        }
        base_name += suffix_separator;
        size_t bn_len = base_name.length(); // Length of basename portion only

        // Add the current name...
        which_names.push_back(index);

        // Gather all other names that are valid for this entity, and
        // match in the first 'bn_len' characters.
        //
        // Check that they have the same number of tokens,
        // It is possible that the first name(s) that match with two
        // suffices have a basename that match other names with only a
        // single suffix lc_cam_x, lc_cam_y, lc_sfarea.
        for (size_t id = index + 1; id < names.size(); id++) {
          const auto &tst_name  = names[id];
          auto        subtokens = field_tokenize(tst_name, suffix_separator);
          if ((truth_table == nullptr || truth_table[id] == 1) && // Defined on this entity
              Ioss::Utils::str_equal(name.substr(0, bn_len),
                                     tst_name.substr(0, bn_len)) && // base portion must match
              (!same_length || tst_name.length() == name_length) &&
              subtokens.size() == num_tokens) {
            which_names.push_back(id);
          }
        }

        const Ioss::VariableType *type = nullptr;
        if (suffix_size == 2) {
          if (which_names.size() > 1) {
            type = match_composite_field(names, which_names, suffix_separator);
          }
        }
        else {
          assert(suffix_size == 1);
          type = match_single_field(names, which_names, suffix_separator, ignore_realn_fields);
        }

        if (type != nullptr) {
          // A valid variable type was recognized.
          // Mark the names which were used so they aren't used for another field on this entity.
          // Create a field of that variable type.
          assert(type->component_count() == static_cast<int>(which_names.size()));
          Ioss::Field field(base_name.substr(0, bn_len - 1), Ioss::Field::REAL, type, fld_role,
                            count);
          if (suffix_separator != '_') {
            field.set_suffix_separator(suffix_separator);
          }
          // Are suffices upper or lowercase...
          auto tmp = field_tokenize(names[which_names[0]], suffix_separator);

          // If a composite variable, then need to check the interior suffix, not the last (which
          // will be 1,2,...)
          bool is_composite = dynamic_cast<const Ioss::CompositeVariableType *>(
                                  field.transformed_storage()) != nullptr;

          Ioss::Suffix suffix{tmp[tmp.size() - (is_composite ? 2 : 1)]};
          field.set_suffices_uppercase(suffix.is_uppercase());
          field.set_index(index);
          for (const auto &which_name : which_names) {
            names[which_name] = "";
          }
          return field;
        }
        if (suffix_size == 1 && !same_length) {
          // Failure recognizing a higher-order field; just take the
          // first field and return it as a scalar...
          Ioss::Field field(name, Ioss::Field::REAL, IOSS_SCALAR(), fld_role, count);

          // Are suffices upper or lowercase...
          auto         tmp = field_tokenize(names[which_names[0]], suffix_separator);
          Ioss::Suffix suffix{tmp[tmp.size() - 1]};
          field.set_suffices_uppercase(suffix.is_uppercase());
          field.set_index(index);
          names[index] = "";
          return field;
        }
      }
      suffix_size--;
    }
    return {"", Ioss::Field::INVALID, IOSS_SCALAR(), fld_role, 1};
  }

  // common
  bool define_field(size_t nmatch, size_t match_length, Ioss::NameList &names, size_t index,
                    std::vector<Ioss::Suffix> &suffices, size_t entity_count,
                    Ioss::Field::RoleType fld_role, std::vector<Ioss::Field> &fields,
                    bool strip_trailing_, bool ignore_realn_fields, char suffix_separator)
  {
    // Try to define a field of size 'nmatch' with the suffices in 'suffices'.
    // If this doesn't define a known field, then assume it is a scalar instead
    // and return false.
    if (nmatch > 1) {
      const Ioss::VariableType *type = Ioss::VariableType::factory(suffices, ignore_realn_fields);
      if (type == nullptr) {
        nmatch = 1;
      }
      else {
        auto name   = names[index].substr(0, match_length);
        auto suffix = suffix_separator;
        if (strip_trailing_ && name.back() == '_') {
          name   = name.substr(0, name.length() - 1);
          suffix = '_';
        }
        Ioss::Field field(name, Ioss::Field::REAL, type, fld_role, entity_count);
        if (suffix != suffix_separator) {
          field.set_suffix_separator(suffix);
        }
        // Are suffices upper or lowercase...
        field.set_suffices_uppercase(suffices[0].is_uppercase());
        if (field.is_valid()) {
          fields.push_back(field);
        }
        for (size_t j = 0; j < nmatch; j++) {
          names[index + j] = "";
        }
        return true;
      }
    }

    // NOTE: nmatch could be reset inside previous if block.
    // This is not an 'else' block, it is a new if block.
    if (nmatch == 1) {
      Ioss::Field field(names[index], Ioss::Field::REAL, IOSS_SCALAR(), fld_role, entity_count);
      if (field.is_valid()) {
        fields.push_back(field);
      }
      names[index] = "";
      return false;
    }
    return false; // Can't get here...  Quiet the compiler
  }
} // namespace

// Read scalar fields off an input database and determine whether
// they are components of a higher order type (vector, tensor, ...).
void Ioss::Utils::get_fields(int64_t         entity_count, // The number of objects in this entity.
                             Ioss::NameList &names,        // Raw list of field names from exodus
                             Ioss::Field::RoleType   fld_role, // Role of field
                             const Ioss::DatabaseIO *db,
                             int                    *local_truth, // Truth table for this entity;
                             // null if not applicable.
                             std::vector<Ioss::Field> &fields) // The fields that were found.
{
  bool enable_field_recognition = db->get_field_recognition();
  bool strip_trailing_          = db->get_field_strip_trailing_();
  bool ignore_realn_fields      = db->get_ignore_realn_fields();
  char suffix_separator         = db->get_field_separator();

  if (!enable_field_recognition) {
    // Create a separate field for each name.
    for (size_t i = 0; i < names.size(); i++) {
      if (!names[i].empty() && (local_truth == nullptr || local_truth[i] == 1)) {
        Ioss::Field field(names[i], Ioss::Field::REAL, IOSS_SCALAR(), fld_role, entity_count);
        field.set_index(i);
        fields.push_back(field);
        names[i] = "";
      }
    }
  }
  else if (suffix_separator != 0) {
    while (true) {
      // NOTE: 'get_next_field' determines storage type (vector, tensor,...)
      Ioss::Field field = get_next_field(names, entity_count, fld_role, suffix_separator,
                                         local_truth, ignore_realn_fields);
      if (field.is_valid()) {
        fields.push_back(field);
      }
      else {
        break;
      }
    }
  }
  else {
    // This routine is used if there is no field component separator.  E.g.,
    // fieldx, fieldy, fieldz instead of field_x field_y field_z
    size_t                    nmatch = 1;
    size_t                    ibeg   = 0;
    size_t                    pmat   = 0;
    std::vector<Ioss::Suffix> suffices;
  top:

    while (ibeg + nmatch < names.size()) {
      if (local_truth != nullptr) {
        while (ibeg < names.size() && local_truth[ibeg] == 0) {
          ibeg++;
        }
      }
      while (ibeg < names.size() && names[ibeg].empty()) {
        ibeg++;
      }
      for (size_t i = ibeg + 1; i < names.size(); i++) {
        auto mat = match(names[ibeg], names[i]);
        if (local_truth != nullptr && local_truth[i] == 0) {
          mat = 0;
        }

        // For all fields, the total length of the name is the same
        // for all components of that field.  The 'basename' of the
        // field will also be the same for all cases.
        //
        // It is possible that the length of the match won't be the
        // same for all components of a field since the match may
        // include a portion of the suffix; (sigxx, sigxy, sigyy
        // should match only 3 characters of the basename (sig), but
        // sigxx and sigxy will match 4 characters) so consider a
        // valid match if the match length is >= previous match length.
        if ((names[ibeg].length() == names[i].length()) && mat > 0 && (pmat == 0 || mat >= pmat)) {
          nmatch++;
          if (nmatch == 2) {
            // Get suffix for first field in the match
            pmat = mat;
            Ioss::Suffix tmp(&names[ibeg][pmat]);
            suffices.push_back(tmp);
          }
          // Get suffix for next fields in the match
          Ioss::Suffix tmp(&names[i][pmat]);
          suffices.push_back(tmp);
        }
        else {

          bool multi_component =
              define_field(nmatch, pmat, names, ibeg, suffices, entity_count, fld_role, fields,
                           strip_trailing_, ignore_realn_fields, suffix_separator);
          if (!multi_component) {
            // Although we matched multiple suffices, it wasn't a
            // higher-order field, so we only used 1 name instead of
            // the 'nmatch' we thought we might use.
            i = ibeg + 1;
          }

          // Cleanout the suffices vector.
          clear(suffices);

          // Reset for the next time through the while loop...
          nmatch = 1;
          pmat   = 0;
          ibeg   = i;
          break;
        }
      }
    }
    // We've gone through the entire list of names; see if what we
    // have forms a multi-component field; if not, then define a
    // scalar field and jump up to the loop again to handle the others
    // that had been gathered.
    if (ibeg < names.size()) {
      if (local_truth == nullptr || local_truth[ibeg] == 1) {
        bool multi_component =
            define_field(nmatch, pmat, names, ibeg, suffices, entity_count, fld_role, fields,
                         strip_trailing_, ignore_realn_fields, suffix_separator);
        clear(suffices);
        if (nmatch > 1 && !multi_component) {
          ibeg++;
          goto top;
        }
      }
      else {
        ibeg++;
        goto top;
      }
    }
  }
}

bool Ioss::Utils::check_int_to_real_overflow(const Ioss::Field &field, int64_t *data,
                                             size_t num_entity)
{
  // Check all values in `data` to make sure that if they are converted to a double and
  // back again, there will be no data loss.  This requires that the value be less than 2^53.
  static int64_t max_double = 2LL << 53;
  assert(int64_t(double(max_double)) == max_double);
  assert(int64_t(double(max_double + 1)) != max_double + 1);

  size_t comp_count = field.get_component_count(Ioss::Field::InOut::OUTPUT);
  for (size_t i = 0; i < num_entity * comp_count; i++) {
    if (data[i] > max_double) {
      fmt::print(
          Ioss::WarnOut(),
          "Field '{}' contains 64-bit integer data that is not representable as a double value.\n"
          "\tThis value can not currently be stored in the exodus database without data loss.\n"
          "\tThe first such value is at location {}, component {} (1-based) with value {}.\n",
          field.get_name(), fmt::group_digits(i / comp_count + 1), i % comp_count + 1,
          fmt::group_digits(data[i]));
      return true;
    }
  }
  return false;
}

std::string Ioss::Utils::platform_information()
{
#if !defined(__IOSS_WINDOWS__)
  struct utsname sys_info
  {
  };
  uname(&sys_info);
  std::string info =
      fmt::format("Node: {0}, OS: {1} {2}, {3}, Machine: {4}", sys_info.nodename, sys_info.sysname,
                  sys_info.release, sys_info.version, sys_info.machine);
#else
  std::string info = "Node: Unknown, OS: Unknown, Machine: Unknown";
#endif
  return info;
}

bool Ioss::Utils::block_is_omitted(Ioss::GroupingEntity *block)
{
  bool omitted = block->get_optional_property("omitted", 0) == 1;
  return omitted;
}

void Ioss::Utils::calculate_sideblock_membership(IntVector             &face_is_member,
                                                 const Ioss::SideBlock *sd_blk,
                                                 size_t int_byte_size, const void *element,
                                                 const void *sides, int64_t number_sides,
                                                 const Ioss::Region *region)
{
  assert(sd_blk != nullptr);

  face_is_member.reserve(number_sides);

  const ElementTopology *unknown = Ioss::ElementTopology::factory("unknown");

  // Topology of faces in this face block...
  const ElementTopology *ftopo = sd_blk->topology();

  // Topology of parent element for faces in this face block
  const ElementTopology *parent_topo = sd_blk->parent_element_topology();

  // If split by element block then parent_block will be non-nullptr
  const ElementBlock *parent_block = sd_blk->parent_element_block();

  // The element block containing the face we are working on...
  Ioss::ElementBlock *block = nullptr;

  // Topology of face/edge in current element block
  const ElementTopology *common_ftopo = nullptr;

  // Topology of elements in the element block containing this element
  const ElementTopology *block_topo = nullptr;

  // Topology of the face we are currently working with...
  const ElementTopology *topo = nullptr;

  // The element side that the current face is on the element...
  int current_side = -1;

  for (int64_t iel = 0; iel < number_sides; iel++) {
    int64_t elem_id = 0;
    int     side_id = 0;
    if (int_byte_size == 4) {
      elem_id = static_cast<const int *>(element)[iel];
      side_id = static_cast<const int *>(sides)[iel];
    }
    else {
      elem_id = static_cast<const int64_t *>(element)[iel];
      side_id = static_cast<int>(static_cast<const int64_t *>(sides)[iel]);
    }

    // Get the element block containing this face...
    if (block == nullptr || !block->contains(elem_id)) {
      block      = region->get_element_block(elem_id);
      block_topo = block->topology();
      // nullptr if hetero face/edge on element
      common_ftopo = block->topology()->boundary_type(0);
      if (common_ftopo != nullptr) {
        topo = common_ftopo;
      }
      current_side = -1;
    }

    // If the element topology of the element block containing this
    // face has heterogeneous topology (eg. wedge), then determine the
    // topology corresponding to the current side..
    if (common_ftopo == nullptr && side_id != current_side) {
      current_side = side_id;
      topo         = block->topology()->boundary_type(side_id);
    }

    bool face_topo_match  = ftopo == unknown || topo == ftopo;
    bool block_topo_match = parent_topo == unknown || block_topo == parent_topo;
    // See if the face topology and the parent element topology for
    // the current face match the topology associated with this face block.
    if (face_topo_match && block_topo_match && (parent_block == nullptr || parent_block == block) &&
        !block_is_omitted(block)) {
      // This face/edge  belongs in the face/edge block
      face_is_member.push_back(1);
    }
    else {
      face_is_member.push_back(0);
    }
  }
}

int64_t Ioss::Utils::get_side_offset(const Ioss::SideBlock *sb)
{

  const Ioss::ElementTopology *side_topo   = sb->topology();
  const Ioss::ElementTopology *parent_topo = sb->parent_element_topology();
  int64_t                      side_offset = 0;
  if ((side_topo != nullptr) && (parent_topo != nullptr)) {
    int side_topo_dim = side_topo->parametric_dimension();
    int elem_topo_dim = parent_topo->parametric_dimension();
    int elem_spat_dim = parent_topo->spatial_dimension();

    if (side_topo_dim + 1 < elem_spat_dim && side_topo_dim < elem_topo_dim) {
      side_offset = parent_topo->number_faces();
    }
  }
  return side_offset;
}

std::string Ioss::Utils::shape_to_string(const Ioss::ElementShape &shape)
{
  switch (shape) {
  case Ioss::ElementShape::UNKNOWN: return {"Unknown"};
  case Ioss::ElementShape::POINT: return {"Point"};
  case Ioss::ElementShape::SPHERE: return {"Sphere"};
  case Ioss::ElementShape::LINE: return {"Line"};
  case Ioss::ElementShape::SPRING: return {"Spring"};
  case Ioss::ElementShape::TRI: return {"Tri"};
  case Ioss::ElementShape::QUAD: return {"Quad"};
  case Ioss::ElementShape::TET: return {"Tet"};
  case Ioss::ElementShape::PYRAMID: return {"Pyramid"};
  case Ioss::ElementShape::WEDGE: return {"Wedge"};
  case Ioss::ElementShape::HEX: return {"Hex"};
  case Ioss::ElementShape::SUPER: return {"Super"};
  }
  return fmt::format("Invalid shape [{}]", unsigned(shape));
}

std::string Ioss::Utils::entity_type_to_string(const Ioss::EntityType &type)
{
  switch (type) {
  case Ioss::EntityType::NODEBLOCK: return {"NODEBLOCK"};
  case Ioss::EntityType::EDGEBLOCK: return {"EDGEBLOCK"};
  case Ioss::EntityType::FACEBLOCK: return {"FACEBLOCK"};
  case Ioss::EntityType::ELEMENTBLOCK: return {"ELEMENTBLOCK"};
  case Ioss::EntityType::NODESET: return {"NODESET"};
  case Ioss::EntityType::EDGESET: return {"EDGESET"};
  case Ioss::EntityType::FACESET: return {"FACESET"};
  case Ioss::EntityType::ELEMENTSET: return {"ELEMENTSET"};
  case Ioss::EntityType::SIDESET: return {"SIDESET"};
  case Ioss::EntityType::COMMSET: return {"COMMSET"};
  case Ioss::EntityType::SIDEBLOCK: return {"SIDEBLOCK"};
  case Ioss::EntityType::REGION: return {"REGION"};
  case Ioss::EntityType::SUPERELEMENT: return {"SUPERELEMENT"};
  case Ioss::EntityType::STRUCTUREDBLOCK: return {"STRUCTUREDBLOCK"};
  case Ioss::EntityType::ASSEMBLY: return {"ASSEMBLY"};
  case Ioss::EntityType::BLOB: return {"BLOB"};
  case Ioss::EntityType::INVALID_TYPE: return {"INVALID_TYPE"};
  }
  return fmt::format("Invalid entity type [{}]", unsigned(type));
}

unsigned int Ioss::Utils::hash(const std::string &name)
{
  // Hash function from Aho, Sethi, Ullman "Compilers: Principles,
  // Techniques, and Tools.  Page 436

  const char  *symbol = name.c_str();
  unsigned int hashval;
  for (hashval = 0; *symbol != '\0'; symbol++) {
    hashval = (hashval << 4) + *symbol;
    auto g  = hashval & 0xf0000000;
    if (g != 0) {
      hashval = hashval ^ (g >> 24);
      hashval = hashval ^ g;
    }
  }
  return hashval;
}

double Ioss::Utils::timer()
{
  auto now = std::chrono::steady_clock::now();
  return std::chrono::duration<double>(now - initial_time).count();
}

void Ioss::Utils::input_file(const std::string &file_name, Ioss::NameList *lines,
                             size_t max_line_length)
{
  // Create an ifstream for the input file. This does almost the same
  // function as sierra::Env::input() except this is for a single
  // processor and the sierra::Env::input() is for parallel...

  if (!file_name.empty()) {
    // Open the file and read into the vector...
    std::string   input_line;
    std::ifstream infile(file_name);
    lines->push_back(file_name.substr(0, max_line_length));
    while (!std::getline(infile, input_line).fail()) {
      if (max_line_length == 0 || input_line.length() <= max_line_length) {
        lines->push_back(input_line);
      }
      else {
        // Split the line into pieces of length "max_line_length-1"
        // and append a "\" to all but the last. Don't worry about
        // splitting at whitespace...
        size_t ibeg = 0;
        do {
          std::string sub = input_line.substr(ibeg, max_line_length - 1);
          if (ibeg + max_line_length - 1 < input_line.length()) {
            sub += "\\";
          }
          lines->push_back(sub);
          ibeg += max_line_length - 1;
        } while (ibeg < input_line.length());
      }
    }
  }
}

bool Ioss::Utils::str_equal(const std::string &s1, const std::string &s2)
{
  return (s1.size() == s2.size()) &&
         std::equal(s1.begin(), s1.end(), s2.begin(),
                    [](char a, char b) { return std::tolower(a) == std::tolower(b); });
}

bool Ioss::Utils::substr_equal(const std::string &prefix, const std::string &str)
{
  return (str.size() >= prefix.size()) && str_equal(prefix, str.substr(0, prefix.size()));
}

std::string Ioss::Utils::capitalize(std::string name)
{
  name[0] = std::toupper(name[0]);
  return name;
}

std::string Ioss::Utils::uppercase(std::string name)
{
  std::transform(name.begin(), name.end(), name.begin(),
                 [](char c) { return static_cast<char>(std::toupper(c)); });
  return name;
}

std::string Ioss::Utils::lowercase(std::string name)
{
  std::transform(name.begin(), name.end(), name.begin(),
                 [](char c) { return static_cast<char>(std::tolower(c)); });
  return name;
}

bool Ioss::Utils::check_set_bool_property(const Ioss::PropertyManager &properties,
                                          const std::string &prop_name, bool &prop_value)
{
  bool found_property = false;
  if (properties.exists(prop_name)) {
    found_property = true;
    if (properties.get(prop_name).get_type() == Ioss::Property::INTEGER) {
      prop_value = properties.get(prop_name).get_int() != 0;
    }
    else {
      std::string yesno = Ioss::Utils::uppercase(properties.get(prop_name).get_string());
      if (yesno == "TRUE" || yesno == "YES" || yesno == "ON") {
        prop_value = true;
      }
      else if (yesno == "FALSE" || yesno == "NO" || yesno == "OFF") {
        prop_value = false;
      }
      else {
        std::ostringstream errmsg;
        fmt::print(errmsg,
                   "ERROR: Unrecognized value found for {}. "
                   "Found '{}' which is not one of TRUE|FALSE|YES|NO|ON|OFF",
                   prop_name, yesno);
        IOSS_ERROR(errmsg);
      }
    }
  }
  return found_property;
}

void Ioss::Utils::fixup_name(char *name)
{
  assert(name != nullptr);

  size_t len = std::strlen(name);
  for (size_t i = 0; i < len; i++) {
    name[i] = static_cast<char>(tolower(name[i])); // guaranteed(?) to be ascii...
    if (name[i] == ' ') {
      name[i] = '_';
    }
  }
}

void Ioss::Utils::fixup_name(std::string &name)
{
  name = Ioss::Utils::lowercase(name);

  size_t len = name.length();
  for (size_t i = 0; i < len; i++) {
    if (name[i] == ' ') {
      name[i] = '_';
    }
  }
}

namespace {

  /** \brief Hash function from Aho, Sethi, Ullman "Compilers: Principles,
   *         Techniques, and Tools.  Page 436
   */
  std::string two_letter_hash(const char *symbol)
  {
    const int    HASHSIZE = 673; // Largest prime less than 676 (26*26)
    unsigned int hashval;
    for (hashval = 0; *symbol != '\0'; symbol++) {
      hashval        = (hashval << 4) + *symbol;
      unsigned int g = hashval & 0xf0000000;
      if (g != 0) {
        hashval = hashval ^ (g >> 24);
        hashval = hashval ^ g;
      }
    }

    // Convert to base-26 'number'
    hashval %= HASHSIZE;
    char word[3] = {char(hashval / 26 + 'a'), char(hashval % 26 + 'a'), '\0'};
    return {word};
  }
} // namespace

std::string Ioss::Utils::variable_name_kluge(const std::string &name, size_t component_count,
                                             size_t copies, size_t max_var_len)
{

  // Width = 'max_var_len'.
  // Reserve space for suffix '_00...'
  // Reserve 3 for hash   '.xx'
  size_t hash_len = 3;
  size_t comp_len = 3; // _00
  size_t copy_len = 0;

  if (copies > 1) {
    assert(component_count % copies == 0);
    component_count /= copies;
  }

  if (component_count <= 1) {
    comp_len = 0;
  }
  else {
    comp_len = number_width(component_count) + 1; // _00000
  }

  if (copies <= 1) {
    copy_len = 0;
  }
  else {
    copy_len = number_width(copies) + 1; // _00000
  }

  size_t maxlen = max_var_len - comp_len - copy_len;

  std::string new_str = name;
  if (name.length() <= maxlen) {
    // If name fits without kluging, then just use name as it is
    // without adding on the hash...
    return lowercase(new_str);
  }
  // Know that the name is too long, try to shorten. Need room for
  // hash now.
  maxlen -= hash_len;
  size_t len = name.length();

  // Take last 'maxlen' characters.  Motivation for this is that the
  // beginning of the composed (or generated) variable name is the
  // names of the mechanics and mechanics instances in which this
  // variable is nested, so they will be similar for all variables at
  // the same scope and the differences will occur at the variable
  // name level...
  //
  // However, there will likely be variables at the
  // same level but in different scope that have the same name which
  // would cause a clash, so we *hope* that the hash will make those
  // scope names unique...
  std::string s = std::string(name).substr(len - maxlen, len);
  assert(s.length() <= maxlen);
  new_str = std::move(s);

  // NOTE: The hash is not added if the name is not shortened.
  std::string hash_string = two_letter_hash(name.c_str());
  new_str += std::string(".");
  new_str += hash_string;
  return lowercase(new_str);
}

void Ioss::Utils::generate_history_mesh(Ioss::Region *region)
{
  Ioss::DatabaseIO *db = region->get_database();
  if (db->parallel_rank() == 0) {
    region->begin_mode(Ioss::STATE_DEFINE_MODEL);

    // Node Block
    auto *nb = new Ioss::NodeBlock(db, "nodeblock_1", 1, 3);
    region->add(nb);

    // Element Block
    auto *eb = new Ioss::ElementBlock(db, "e1", "sphere", 1);
    eb->property_add(Ioss::Property("id", 1));
    eb->property_add(Ioss::Property("guid", 1));
    region->add(eb);
    region->end_mode(Ioss::STATE_DEFINE_MODEL);

    region->begin_mode(Ioss::STATE_MODEL);
    static double coord[3] = {1.1, 2.2, 3.3};
    static int    ids[1]   = {1};
    nb->put_field_data("ids", ids, sizeof(int));
    nb->put_field_data("mesh_model_coordinates", coord, 3 * sizeof(double));

    static int connect[1] = {1};
    eb->put_field_data("ids", ids, sizeof(int));
    eb->put_field_data("connectivity", connect, 1 * sizeof(int));

    region->end_mode(Ioss::STATE_MODEL);
  }
}

// Safer than strcpy -- guarantees null termination
void Ioss::Utils::copy_string(char *dest, char const *source, size_t elements)
{
  char *d;
  for (d = dest; d + 1 < dest + elements && *source; d++, source++) {
    *d = *source;
  }
  *d = '\0';
}

namespace {
  const int tab64[64] = {63, 0,  58, 1,  59, 47, 53, 2,  60, 39, 48, 27, 54, 33, 42, 3,
                         61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22, 4,
                         62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21,
                         56, 45, 25, 31, 35, 16, 9,  12, 44, 24, 15, 8,  23, 7,  6,  5};
} // namespace

int Ioss::Utils::log_power_2(uint64_t value)
{
  assert(value > 0);
  value = (value << 1) - 1;
  value |= value >> 1;
  value |= value >> 2;
  value |= value >> 4;
  value |= value >> 8;
  value |= value >> 16;
  value |= value >> 32;
  return tab64[(((value - (value >> 1)) * 0x07EDD5E59A4E28C2)) >> 58];
}

int Ioss::Utils::term_width()
{
  int cols = 0;
  if (isatty(fileno(stdout))) {
#if defined(TIOCGWINSZ)
    struct winsize ts;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &ts);
    cols = ts.ws_col;
#elif TIOCGSIZE
    struct ttysize ts;
    ioctl(STDOUT_FILENO, TIOCGSIZE, &ts);
    cols = ts.ts_cols;
#endif /* TIOCGSIZE */
  }
  return cols != 0 ? cols : 100;
}

std::string Ioss::Utils::get_type_from_file(const std::string &filename)
{
  Ioss::FileInfo file(filename);
  auto           extension = file.extension();

  // If the extension is numeric, then we are probably dealing with a single file of a
  // set of FPP decomposed files (e.g. file.cgns.32.17).  In that case, we tokenize
  // with "." as delimiter and see if last two tokens are all digits and if there
  // are at least 4 tokens (basename.extension.#proc.proc)...
  bool all_dig = extension.find_first_not_of("0123456789") == std::string::npos;
  if (all_dig) {
    auto tokens = Ioss::tokenize(filename, ".");
    if (tokens.size() >= 4) {
      const auto &proc_count = tokens[tokens.size() - 2];
      if (proc_count.find_first_not_of("0123456789") == std::string::npos) {
        extension = tokens[tokens.size() - 3];
      }
    }
  }

  if (extension == "e" || extension == "g" || extension == "gen" || extension == "exo") {
    return "exodus";
  }
  if (extension == "cgns") {
    return "cgns";
  }
  // "exodus" is default...
  return "exodus";
}

void Ioss::Utils::info_fields(const Ioss::GroupingEntity *ige, Ioss::Field::RoleType role,
                              const std::string &header, const std::string &suffix, bool detail)
{
  Ioss::NameList fields = ige->field_describe(role);

  if (fields.empty()) {
    return;
  }

  if (!header.empty()) {
    fmt::print("{}{}", header, suffix);
  }
  // Iterate through results fields. Get max width of a name...
  size_t max_width = 0;
  for (const auto &field_name : fields) {
    max_width = max_width > field_name.length() ? max_width : field_name.length();
  }

  size_t width   = Ioss::Utils::term_width();
  size_t cur_out = 8; // Tab width...
  if (!header.empty()) {
    cur_out = header.size() + suffix.size() + 16; // Assume 2 tabs...
  }
  for (const auto &field_name : fields) {
    if (detail) {
      const auto &field_ref = ige->get_fieldref(field_name);
      fmt::print("{}{}", field_ref, suffix);
    }
    else {
      const Ioss::VariableType *var_type   = ige->get_field(field_name).raw_storage();
      int                       comp_count = var_type->component_count();
      fmt::print("{1:>{0}s}:{2}  ", max_width, field_name, comp_count);
      cur_out += max_width + 4;
      if (cur_out + max_width >= width) {
        fmt::print("{}", suffix);
        cur_out = 8;
      }
    }
  }
  if (!header.empty()) {
    fmt::print("\n");
  }
}

void Ioss::Utils::info_property(const Ioss::GroupingEntity *ige, Ioss::Property::Origin origin,
                                const std::string &header, const std::string &suffix,
                                bool print_empty)
{
  Ioss::NameList properties = ige->property_describe(origin);

  if (properties.empty()) {
    if (print_empty && !header.empty()) {
      fmt::print("{}{} *** No attributes ***\n", header, suffix);
    }
    return;
  }

  if (!header.empty()) {
    fmt::print("{}{}", header, suffix);
  }

  int num_out = 0;
  for (const auto &property_name : properties) {
    fmt::print("{:>s}: ", property_name);
    auto prop = ige->get_property(property_name);
    switch (prop.get_type()) {
    case Ioss::Property::BasicType::REAL: fmt::print("{}\t", prop.get_real()); break;
    case Ioss::Property::BasicType::INTEGER: fmt::print("{}\t", prop.get_int()); break;
    case Ioss::Property::BasicType::STRING: fmt::print("'{}'\t", prop.get_string()); break;
    case Ioss::Property::BasicType::VEC_INTEGER:
      fmt::print("{}\t", fmt::join(prop.get_vec_int(), "  "));
      break;
    case Ioss::Property::BasicType::VEC_DOUBLE:
      fmt::print("{}\t", fmt::join(prop.get_vec_double(), "  "));
      break;
    default:; // Do nothing
    }
    num_out++;
    if (num_out >= 3) {
      fmt::print("\n\t");
      num_out = 0;
    }
  }
  if (!header.empty()) {
    fmt::print("\n");
  }
}

void Ioss::Utils::copyright(std::ostream &out, const std::string &year_range)
{
  fmt::print(out,
             "\n"
             "Copyright(C) {} National Technology & Engineering Solutions of\n"
             "Sandia, LLC (NTESS).  Under the terms of Contract DE-NA0003525 with\n"
             "NTESS, the U.S. Government retains certain rights in this software.\n"
             "\n"
             "Redistribution and use in source and binary forms, with or without\n"
             "modification, are permitted provided that the following conditions are\n"
             "met:\n"
             "\n"
             "* Redistributions of source code must retain the above copyright\n"
             "   notice, this list of conditions and the following disclaimer.\n"
             "\n"
             "* Redistributions in binary form must reproduce the above\n"
             "  copyright notice, this list of conditions and the following\n"
             "  disclaimer in the documentation and/or other materials provided\n"
             "  with the distribution.\n"
             "\n"
             "* Neither the name of NTESS nor the names of its\n"
             "  contributors may be used to endorse or promote products derived\n"
             "  from this software without specific prior written permission.\n"
             "\n"
             "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n"
             "\" AS IS \" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n"
             "LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n"
             "A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n"
             "OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n"
             "SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n"
             "LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"
             "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"
             "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"
             "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"
             "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
             "\n",
             year_range);
}
