/*
 * Copyright(C) 1999-2025 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
 */
/* exodus II to matlab m file, copy of
   exo2mat.
   exo2mat was written by mrtabba
   exo2m modifications by gmreese to permit usage on machines without
     the matlab libraries.

   modified by D. Todd Griffith, 01/12/2006
      to include changes made in versions 1.4 through 1.6 on the
      SEACAS tools repository as the previous modifications dated
      12/08 and 12/15/2005 were made starting with Version 1.3.
      In particular, the only changes are those made here are those
      made in Version 1.6 which include a special provision
      for exodus files which contain no distribution factors
   modified by D. Todd Griffith, 12/15/2005
      to write distribution factors as double precision type
   modified by D. Todd Griffith  12/08/2005
      to include complete writing of side set and node set information
      so it will be available for the mat2exo conversion stage

*/

#include <algorithm>
#include <array>
#include <cstring> // for strlen, etc
#include <fmt/chrono.h>
#include <fmt/ostream.h>
#include <fmt/printf.h>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>

#include "add_to_log.h" // for add_to_log
#include "exodusII.h"   // for ex_get_variable_param, etc
#include "matio.h"      // for Mat_VarCreate, Mat_VarFree, etc
#include "time_stamp.h"
#include "vector_data.h"

#include <cassert> // for assert
#include <cstddef> // for size_t
#include <cstdlib> // for free, calloc, exit
#include <ctime>
#if MATIO_VERSION < 151
#error "MatIO Version 1.5.1 or greater is required"
#endif

namespace {
  const std::string EXT      = ".mat";
  int               textfile = 0;

  FILE  *m_file   = nullptr; /* file for m file output */
  mat_t *mat_file = nullptr; /* file for binary .mat output */
  bool   debug    = false;
  bool   do_info  = true;

  const std::array<std::string, 3> qainfo{"exo2mat", "2025/04/10", "4.10"};

  void logger(const char *message)
  {
    const std::string tsFormat = "[%H:%M:%S] ";
    fmt::print(std::clog, "{}: {}\n", time_stamp(tsFormat), message);
  }

  void usage()
  {
    const auto *v5default = (MAT_FT_DEFAULT == MAT_FT_MAT5 ? "[default]" : "");
    const auto *v7default = (MAT_FT_DEFAULT == MAT_FT_MAT73 ? "[default]" : "");
    fmt::print("exo2mat [options] exodus_file_name.\n"
               "   the exodus_file_name is required (exodus only).\n"
               "   Options:\n"
               "   -t    write a text (.m) file rather than a binary .mat\n"
               "   -o    output file name (rather than auto generate)\n"
               "   -i    do *NOT* output the info records to mat file\n"
               "   -c    use cell arrays for transient variables.\n"
               "   -v5   output version 5 mat file {}\n"
               "   -v73  output version 7.3 mat file (hdf5-based) {}\n"
               "   -v7.3 output version 7.3 mat file (hdf5-based) {}\n"
               "   -change_set cs_#  Read from change set #.\n"
               " ** note **\n"
               "Binary files are written by default on all platforms.\n",
               v5default, v7default, v7default);
  }

  /* put a string into an m file. If the string has
     line feeds, we put it as ints, and use 'char()' to convert it */
  void mPutStr(const std::string &name, const char *str)
  {
    assert(m_file != nullptr);
    if (strchr(str, '\n') == nullptr) {
      fmt::fprintf(m_file, "%s='%s';\n", name, str);
    }
    else {
      fmt::fprintf(m_file, "%s=[", name);
      size_t i;
      size_t j;
      for (j = i = 0; i < std::strlen(str); i++, j++) {
        if (j >= 20) {
          j = 0;
          fmt::fprintf(m_file, "...\n");
        }
        fmt::fprintf(m_file, "%d ", str[i]);
      }
      fmt::fprintf(m_file, "];\n");
      fmt::fprintf(m_file, "%s=char(%s);\n", name, name);
    }
  }

  /* put double array in m file */
  void mPutDbl(const std::string &name, int n1, int n2, double *pd)
  {
    assert(m_file != nullptr);
    if (n1 == 1 && n2 == 1) {
      fmt::fprintf(m_file, "%s=%15.8e;\n", name, *pd);
      return;
    }
    fmt::fprintf(m_file, "%s=zeros(%d,%d);\n", name, n1, n2);
    for (int i = 0; i < n1; i++) {
      for (int j = 0; j < n2; j++) {
        fmt::fprintf(m_file, "%s(%d,%d)=%15.8e;\n", name, i + 1, j + 1, pd[i * n2 + j]);
      }
    }
  }

  /* put integer array in m file */
  void mPutInt(const std::string &name, int pd)
  {
    assert(m_file != nullptr);
    fmt::fprintf(m_file, "%s=%d;\n", name, pd);
  }

  /* put integer array in m file */
  void mPutInt(const std::string &name, int n1, int n2, int *pd)
  {
    assert(m_file != nullptr);
    if (n1 == 1 && n2 == 1) {
      fmt::fprintf(m_file, "%s=%d;\n", name, *pd);
      return;
    }
    fmt::fprintf(m_file, "%s=zeros(%d,%d);\n", name, n1, n2);
    for (int i = 0; i < n1; i++) {
      for (int j = 0; j < n2; j++) {
        fmt::fprintf(m_file, "%s(%d,%d)=%d;\n", name, i + 1, j + 1, pd[i * n2 + j]);
      }
    }
  }

  /* put string in mat file*/
  int matPutStr(const std::string &name, char *str)
  {
    int       error  = 0;
    matvar_t *matvar = nullptr;
    size_t    dims[2];

    dims[0] = 1;
    dims[1] = std::strlen(str);

    matvar =
        Mat_VarCreate(name.c_str(), MAT_C_CHAR, MAT_T_UINT8, 2, dims, str, MAT_F_DONT_COPY_DATA);
    if (matvar != nullptr) {
      error = Mat_VarWrite(mat_file, matvar, MAT_COMPRESSION_NONE);
      Mat_VarFree(matvar);
    }
    else {
      error = 1;
    }
    return error;
  }

  /* put double in mat file*/
  int matPutDbl(const std::string &name, int n1, int n2, double *pd)
  {
    int       error  = 0;
    matvar_t *matvar = nullptr;

    size_t dims[2];
    dims[0] = n1;
    dims[1] = n2;

    matvar =
        Mat_VarCreate(name.c_str(), MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims, pd, MAT_F_DONT_COPY_DATA);
    if (matvar != nullptr) {
      error = Mat_VarWrite(mat_file, matvar, MAT_COMPRESSION_ZLIB);
      Mat_VarFree(matvar);
    }
    else {
      error = 1;
    }
    return error;
  }

  /* put integer in mat file*/
  int matPutInt(const std::string &name, int n1, int n2, int *pd)
  {
    int       error  = 0;
    matvar_t *matvar = nullptr;

    size_t dims[2];
    dims[0] = n1;
    dims[1] = n2;

    matvar =
        Mat_VarCreate(name.c_str(), MAT_C_INT32, MAT_T_INT32, 2, dims, pd, MAT_F_DONT_COPY_DATA);
    if (matvar != nullptr) {
      error = Mat_VarWrite(mat_file, matvar, MAT_COMPRESSION_ZLIB);
      Mat_VarFree(matvar);
    }
    else {
      error = 1;
    }
    return error;
  }

  /* wrappers for the output routine types */
  void PutStr(const std::string &name, const std::string &str)
  {
    if (textfile != 0) {
      mPutStr(name, str.c_str());
    }
    else {
      matPutStr(name, const_cast<char *>(str.c_str()));
    }
  }

  int PutInt(const std::string &name, int pd)
  {
    int error = 0;
    if (textfile != 0) {
      mPutInt(name, pd);
    }
    else {
      error = matPutInt(name, 1, 1, &pd);
    }
    return error;
  }

  int PutInt(const std::string &name, int n1, int n2, int *pd)
  {
    int error = 0;
    if (textfile != 0) {
      mPutInt(name, n1, n2, pd);
    }
    else {
      error = matPutInt(name, n1, n2, pd);
    }
    return error;
  }

  int PutDbl(const std::string &name, int n1, int n2, double *pd)
  {
    int error = 0;
    if (textfile != 0) {
      mPutDbl(name, n1, n2, pd);
    }
    else {
      error = matPutDbl(name, n1, n2, pd);
    }
    return error;
  }

  char **get_exodus_names(size_t count, int size)
  {
    auto *names = new char *[count];
    for (size_t i = 0; i < count; i++) {
      names[i] = new char[size + 1];
      std::memset(names[i], '\0', size + 1);
    }
    return names;
  }

  void delete_exodus_names(char **names, int count)
  {
    for (int i = 0; i < count; i++) {
      delete[] names[i];
    }
    delete[] names;
  }

  void get_put_user_names(int exo_file, ex_entity_type type, int num_blocks,
                          const std::string &mname)
  {
    int max_name_length = ex_inquire_int(exo_file, EX_INQ_DB_MAX_USED_NAME_LENGTH);
    max_name_length     = max_name_length < 32 ? 32 : max_name_length;
    ex_set_max_name_length(exo_file, max_name_length);
    char **names = get_exodus_names(num_blocks, max_name_length + 1);
    ex_get_names(exo_file, type, names);

    std::string user_names;
    for (int j = 0; j < num_blocks; j++) {
      user_names += names[j];
      user_names += "\n";
    }
    PutStr(mname, user_names);
    delete_exodus_names(names, num_blocks);
  }

  void get_put_names(int exo_file, ex_entity_type type, int num_vars, const std::string &mname)
  {
    int max_name_length = ex_inquire_int(exo_file, EX_INQ_DB_MAX_USED_NAME_LENGTH);
    max_name_length     = max_name_length < 32 ? 32 : max_name_length;
    char **names        = get_exodus_names(num_vars, max_name_length + 1);

    if (debug) {
      logger("\tReading variable names");
    }
    ex_get_variable_names(exo_file, type, num_vars, names);

    std::string mat;
    for (int i = 0; i < num_vars; i++) {
      mat += names[i];
      mat += "\n";
    }
    if (debug) {
      logger("\tWriting variable names");
    }
    PutStr(mname, mat);

    delete_exodus_names(names, num_vars);
  }

  std::vector<std::string> get_names(int exo_file, ex_entity_type type, int num_vars)
  {
    int max_name_length = ex_inquire_int(exo_file, EX_INQ_DB_MAX_USED_NAME_LENGTH);
    max_name_length     = max_name_length < 32 ? 32 : max_name_length;
    char **names        = get_exodus_names(num_vars, max_name_length + 1);

    if (debug) {
      logger("\tReading variable names");
    }
    ex_get_variable_names(exo_file, type, num_vars, names);

    std::vector<std::string> mat(num_vars);
    for (int i = 0; i < num_vars; i++) {
      mat[i] = names[i];
    }
    delete_exodus_names(names, num_vars);
    return mat;
  }

  void get_put_vars(int exo_file, ex_entity_type type, int num_blocks, int num_vars,
                    int num_time_steps, const std::vector<int> &num_per_block,
                    const std::string &prefix, bool use_cell_arrays)

  {
    /* truth table */
    if (debug) {
      logger("\tTruth Table");
    }
    std::vector<int> truth_table(num_vars * num_blocks);
    ex_get_truth_table(exo_file, type, num_blocks, num_vars, Data(truth_table));

    std::vector<int> ids(num_blocks);
    ex_get_ids(exo_file, type, Data(ids));

    size_t num_entity = std::accumulate(num_per_block.begin(), num_per_block.end(), 0);

    if (use_cell_arrays) {
      std::string var_name = prefix + "var";

      size_t dims[2];
      dims[0] = 2;
      dims[1] = num_vars;
      matvar_t *cell_array =
          Mat_VarCreate(var_name.c_str(), MAT_C_CELL, MAT_T_CELL, 2, dims, nullptr, 0);
      assert(cell_array);

      std::vector<double> scr(static_cast<size_t>(num_vars) * num_time_steps * num_entity);
      dims[0]       = num_entity;
      dims[1]       = num_time_steps;
      size_t offset = 0;

      // Get vector of variable names...
      auto names = get_names(exo_file, type, num_vars);

      std::vector<matvar_t *> cell_element(num_vars * 2);

      int j = 0;
      for (int i = 0; i < num_vars; i++) {
        size_t sdims[2];
        sdims[0]        = 1;
        sdims[1]        = names[i].length();
        cell_element[j] = Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8, 2, sdims,
                                        (void *)names[i].c_str(), MAT_F_DONT_COPY_DATA);
        Mat_VarSetCell(cell_array, j, cell_element[j]);
        j++;

        cell_element[j] = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims, &scr[offset],
                                        MAT_F_DONT_COPY_DATA);
        assert(cell_element[j]);
        Mat_VarSetCell(cell_array, j, cell_element[j]);
        size_t n = 0;
        for (int jj = 0; jj < num_time_steps; jj++) {
          for (int k = 0; k < num_blocks; k++) {
            if (truth_table[num_vars * k + i] == 1) {
              ex_get_var(exo_file, jj + 1, type, i + 1, ids[k], num_per_block[k], &scr[n + offset]);
            }
            n += num_per_block[k];
          }
        }
        offset += num_time_steps * num_entity;
        j++;
      }
      Mat_VarWrite(mat_file, cell_array, MAT_COMPRESSION_NONE);
      Mat_VarFree(cell_array);
    }
    else {
      std::string var_name = prefix + "names";
      get_put_names(exo_file, type, num_vars, var_name);

      std::vector<double> scr(num_entity * num_time_steps);

      std::string format = prefix + "var%02d";
      for (int i = 0; i < num_vars; i++) {
        if (debug) {
          logger("\tReading");
        }
        std::fill(scr.begin(), scr.end(), 0.0);
        size_t      n   = 0;
        std::string str = fmt::sprintf(format.c_str(), i + 1);
        for (int j = 0; j < num_time_steps; j++) {
          for (int k = 0; k < num_blocks; k++) {
            if (truth_table[num_vars * k + i] == 1) {
              ex_get_var(exo_file, j + 1, type, i + 1, ids[k], num_per_block[k], &scr[n]);
            }
            n = n + num_per_block[k];
          }
        }
        if (debug) {
          logger("\tWriting");
        }
        PutDbl(str, num_entity, num_time_steps, Data(scr));
      }
    }
  }

  std::vector<int> handle_element_blocks(int exo_file, int num_blocks, bool use_cell_arrays)
  {
    std::vector<int> ids(num_blocks);
    ex_get_ids(exo_file, EX_ELEM_BLOCK, Data(ids));

    std::vector<int> num_elem_in_block(num_blocks);

    // Storing:
    // 1) name
    // 2) id
    // 3) block topology type
    // 4) connectivity
    int max_name_length = ex_inquire_int(exo_file, EX_INQ_DB_MAX_USED_NAME_LENGTH);
    max_name_length     = max_name_length < 32 ? 32 : max_name_length;
    if (use_cell_arrays) {
      int    num_field = 4;
      size_t dims[2];
      dims[0] = num_field;
      dims[1] = num_blocks;
      matvar_t *cell_array =
          Mat_VarCreate("element_blocks", MAT_C_CELL, MAT_T_CELL, 2, dims, nullptr, 0);
      assert(cell_array);

      std::vector<matvar_t *> cell_element(num_blocks * num_field);

      std::vector<int> num_node_per_elem(num_blocks);

      size_t                   conn_size = 0;
      std::vector<std::string> types(num_blocks);

      for (int i = 0; i < num_blocks; i++) {
        char type[33];
        int  num_elem = 0;
        int  num_node = 0;
        int  num_attr = 0;
        ex_get_block(exo_file, EX_ELEM_BLOCK, ids[i], type, &num_elem, &num_node, nullptr, nullptr,
                     &num_attr);
        types[i]             = std::string(type);
        num_elem_in_block[i] = num_elem;
        num_node_per_elem[i] = num_node;
        conn_size += static_cast<size_t>(num_elem) * num_node;
      }

      std::vector<int> connect(conn_size);
      size_t           conn_off = 0;
      for (int i = 0; i < num_blocks; i++) {
        std::vector<char> name(max_name_length + 1);
        ex_get_name(exo_file, EX_ELEM_BLOCK, ids[i], Data(name));
        dims[0]      = 1;
        dims[1]      = std::strlen(Data(name));
        size_t index = num_field * i + 0;
        cell_element[index] =
            Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8, 2, dims, (void *)Data(name), 0);
        Mat_VarSetCell(cell_array, index, cell_element[index]);

        dims[0]             = 1;
        dims[1]             = 1;
        index               = num_field * i + 1;
        cell_element[index] = Mat_VarCreate(nullptr, MAT_C_INT32, MAT_T_INT32, 2, dims, &ids[i],
                                            MAT_F_DONT_COPY_DATA);
        Mat_VarSetCell(cell_array, index, cell_element[index]);

        dims[0] = 1;
        dims[1] = types[i].length();
        index   = num_field * i + 2;
        cell_element[index] =
            Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8, 2, dims, (void *)types[i].c_str(), 0);
        Mat_VarSetCell(cell_array, index, cell_element[index]);

        dims[0] = num_node_per_elem[i];
        dims[1] = num_elem_in_block[i];
        index   = num_field * i + 3;
        ex_get_conn(exo_file, EX_ELEM_BLOCK, ids[i], &connect[conn_off], nullptr, nullptr);
        cell_element[index] = Mat_VarCreate(nullptr, MAT_C_INT32, MAT_T_INT32, 2, dims,
                                            &connect[conn_off], MAT_F_DONT_COPY_DATA);
        assert(cell_element[index]);
        Mat_VarSetCell(cell_array, index, cell_element[index]);

        conn_off += num_node_per_elem[i] * num_elem_in_block[i];
      }
      Mat_VarWrite(mat_file, cell_array, MAT_COMPRESSION_NONE);
      Mat_VarFree(cell_array);
    }
    else {
      std::vector<int>    connect;
      std::vector<double> attr;

      PutInt("blkids", num_blocks, 1, Data(ids));
      std::vector<char> type(max_name_length + 1);
      std::string       types;
      for (int i = 0; i < num_blocks; i++) {
        int num_elem = 0;
        int num_node = 0;
        int num_attr = 0;
        ex_get_block(exo_file, EX_ELEM_BLOCK, ids[i], Data(type), &num_elem, &num_node, nullptr,
                     nullptr, &num_attr);
        types += Data(type);
        types += "\n";
        num_elem_in_block[i] = num_elem;
        connect.resize(static_cast<size_t>(num_elem) * num_node);
        ex_get_conn(exo_file, EX_ELEM_BLOCK, ids[i], Data(connect), nullptr, nullptr);
        std::string str = fmt::sprintf("blk%02d", i + 1);
        PutInt(str, num_node, num_elem, Data(connect));

        // Handle block attributes (if any...)
        attr.resize(num_elem);
        str = fmt::sprintf("blk%02d_nattr", i + 1);
        PutInt(str, num_attr);
        if (num_attr > 0) {
          std::string attr_names;
          char      **names = get_exodus_names(num_attr, max_name_length + 1);
          ex_get_attr_names(exo_file, EX_ELEM_BLOCK, ids[i], names);
          for (int j = 0; j < num_attr; j++) {
            attr_names += names[j];
            attr_names += "\n";
          }
          str = fmt::sprintf("blk%02d_attrnames", i + 1);
          PutStr(str, attr_names);
          delete_exodus_names(names, num_attr);

          for (int j = 0; j < num_attr; j++) {
            str = fmt::sprintf("blk%02d_attr%02d", i + 1, j + 1);
            ex_get_one_attr(exo_file, EX_ELEM_BLOCK, ids[i], j + 1, Data(attr));
            PutDbl(str, num_elem, 1, Data(attr));
          }
        }
      }

      get_put_user_names(exo_file, EX_ELEM_BLOCK, num_blocks, "blkusernames");
      PutStr("blknames", types);
    }
    return num_elem_in_block;
  }

  std::vector<int> handle_node_sets(int exo_file, int num_sets, bool use_cell_arrays)
  {
    std::vector<int> num_nodes(num_sets);
    if (num_sets > 0) {
      if (debug) {
        logger("Node Sets");
      }
      std::vector<int> ids(num_sets);
      ex_get_ids(exo_file, EX_NODE_SET, Data(ids));

      size_t           tot_nodes = 0;
      size_t           tot_dfac  = 0;
      std::vector<int> num_df(num_sets);
      for (int i = 0; i < num_sets; i++) {
        int n1;
        int n2;
        ex_get_set_param(exo_file, EX_NODE_SET, ids[i], &n1, &n2);
        num_nodes[i] = n1;
        num_df[i]    = n2;
        tot_nodes += n1;
        tot_dfac += n2;
      }

      // Storing:
      // 1) name
      // 2) id
      // 3) node list
      // 4) distribution factors
      if (use_cell_arrays) {
        size_t dims[2];
        dims[0] = 4;
        dims[1] = num_sets;
        matvar_t *cell_array =
            Mat_VarCreate("node_sets", MAT_C_CELL, MAT_T_CELL, 2, dims, nullptr, 0);
        assert(cell_array);

        std::vector<matvar_t *> cell_element(num_sets * 4);

        std::vector<int>    node_list(tot_nodes);
        std::vector<double> dist_fac(tot_dfac);
        size_t              nl_off = 0;
        size_t              df_off = 0;

        int max_name_length = ex_inquire_int(exo_file, EX_INQ_DB_MAX_USED_NAME_LENGTH);
        max_name_length     = max_name_length < 32 ? 32 : max_name_length;

        for (int i = 0; i < num_sets; i++) {
          std::vector<char> name(max_name_length + 1);
          ex_get_name(exo_file, EX_NODE_SET, ids[i], Data(name));
          dims[0]      = 1;
          dims[1]      = std::strlen(Data(name));
          size_t index = 4 * i + 0;
          cell_element[index] =
              Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8, 2, dims, (void *)Data(name), 0);
          Mat_VarSetCell(cell_array, index, cell_element[index]);

          dims[0]             = 1;
          dims[1]             = 1;
          index               = 4 * i + 1;
          cell_element[index] = Mat_VarCreate(nullptr, MAT_C_INT32, MAT_T_INT32, 2, dims, &ids[i],
                                              MAT_F_DONT_COPY_DATA);
          Mat_VarSetCell(cell_array, index, cell_element[index]);

          dims[0] = num_nodes[i];
          dims[1] = 1;
          index   = 4 * i + 2;
          ex_get_set(exo_file, EX_NODE_SET, ids[i], &node_list[nl_off], nullptr);
          /* nodes list */
          cell_element[index] = Mat_VarCreate(nullptr, MAT_C_INT32, MAT_T_INT32, 2, dims,
                                              &node_list[nl_off], MAT_F_DONT_COPY_DATA);
          assert(cell_element[index]);
          Mat_VarSetCell(cell_array, index, cell_element[index]);

          /* distribution-factors list */
          ex_get_set_dist_fact(exo_file, EX_NODE_SET, ids[i], &dist_fac[df_off]);
          index               = 4 * i + 3;
          cell_element[index] = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims,
                                              &dist_fac[df_off], MAT_F_DONT_COPY_DATA);
          assert(cell_element[index]);
          Mat_VarSetCell(cell_array, index, cell_element[index]);

          nl_off += num_nodes[i];
          df_off += num_df[i];
        }
        Mat_VarWrite(mat_file, cell_array, MAT_COMPRESSION_NONE);
        Mat_VarFree(cell_array);
      }
      else {
        PutInt("nsids", num_sets, 1, Data(ids));

        for (int i = 0; i < num_sets; i++) {
          std::vector<int> node_list(num_nodes[i]);
          ex_get_set(exo_file, EX_NODE_SET, ids[i], Data(node_list), nullptr);
          /* nodes list */
          std::string str;
          str = fmt::sprintf("nsnod%02d", i + 1);
          PutInt(str, node_list.size(), 1, Data(node_list));

          /* distribution-factors list */
          if (num_df[i] > 0) {
            std::vector<double> dist_fac(num_df[i]);
            ex_get_set_dist_fact(exo_file, EX_NODE_SET, ids[i], Data(dist_fac));
            str = fmt::sprintf("nsfac%02d", i + 1);
            PutDbl(str, dist_fac.size(), 1, Data(dist_fac));
          }
        }
      }

      get_put_user_names(exo_file, EX_NODE_SET, num_sets, "nsusernames");

      /* Store # nodes and # dis. factors per node set */
      PutInt("nnsnodes", num_sets, 1, Data(num_nodes));
      PutInt("nnsdfac", num_sets, 1, Data(num_df));
    }
    return num_nodes;
  }

  std::vector<int> handle_side_sets(int exo_file, int num_sets, bool use_cell_arrays)
  {
    std::vector<int> num_sideset_sides(num_sets);
    std::vector<int> num_sideset_dfac(num_sets);
    std::vector<int> num_sideset_nodes(num_sets);
    if (num_sets > 0) {
      std::vector<int> ids(num_sets);
      ex_get_ids(exo_file, EX_SIDE_SET, Data(ids));

      // Storing:
      // 1) name
      // 2) id
      // 3) element list
      // 4) side list
      // 5) node count per face
      // 6) face node list
      // 7) distribution factors

      if (use_cell_arrays) {
        size_t dims[2];
        dims[0] = 7;
        dims[1] = num_sets;
        matvar_t *cell_array =
            Mat_VarCreate("side_sets", MAT_C_CELL, MAT_T_CELL, 2, dims, nullptr, 0);
        assert(cell_array);

        std::vector<matvar_t *> cell_element(num_sets * 7);

        size_t           num_sides = ex_inquire_int(exo_file, EX_INQ_SS_ELEM_LEN);
        size_t           num_nodes = ex_inquire_int(exo_file, EX_INQ_SS_NODE_LEN);
        std::vector<int> elem_list(num_sides);
        std::vector<int> side_list(num_sides);
        std::vector<int> num_nodes_per_side(num_sides);
        std::vector<int> side_nodes(num_nodes);

        // size_t num_df    = ex_inquire_int(exo_file, EX_INQ_SS_DF_LEN);
        // If `num_df == 0` or if it isn't equal to `num_nodes`, then
        // all df will be set to 1.0, but in any case, the size of
        // `ssdfac` should be num_nodes and not num_df.
        std::vector<double> ssdfac(num_nodes);

        size_t side_off = 0;
        size_t node_off = 0;
        size_t df_off   = 0;

        int max_name_length = ex_inquire_int(exo_file, EX_INQ_DB_MAX_USED_NAME_LENGTH);
        max_name_length     = max_name_length < 32 ? 32 : max_name_length;

        for (int i = 0; i < num_sets; i++) {
          std::vector<char> name(max_name_length + 1);
          ex_get_name(exo_file, EX_SIDE_SET, ids[i], Data(name));
          dims[0]      = 1;
          dims[1]      = std::strlen(Data(name));
          size_t index = 7 * i + 0;
          cell_element[index] =
              Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8, 2, dims, (void *)Data(name), 0);
          Mat_VarSetCell(cell_array, index, cell_element[index]);

          dims[0]             = 1;
          dims[1]             = 1;
          index               = 7 * i + 1;
          cell_element[index] = Mat_VarCreate(nullptr, MAT_C_INT32, MAT_T_INT32, 2, dims, &ids[i],
                                              MAT_F_DONT_COPY_DATA);
          Mat_VarSetCell(cell_array, index, cell_element[index]);

          int n1;
          int n2;
          ex_get_set_param(exo_file, EX_SIDE_SET, ids[i], &n1, &n2);
          num_sideset_sides[i] = n1;
          num_sideset_dfac[i]  = n2;

          bool has_ss_dfac = (n2 != 0 && n1 != n2);
          if (!has_ss_dfac) {
            num_sideset_dfac[i] = num_sideset_nodes[i];
          }

          ex_get_side_set_node_list_len(exo_file, ids[i], &num_sideset_nodes[i]);

          /* element and side list for side sets (dgriffi) */
          ex_get_set(exo_file, EX_SIDE_SET, ids[i], &elem_list[side_off], &side_list[side_off]);
          dims[0]             = num_sideset_sides[i];
          dims[1]             = 1;
          index               = 7 * i + 2;
          cell_element[index] = Mat_VarCreate(nullptr, MAT_C_INT32, MAT_T_INT32, 2, dims,
                                              &elem_list[side_off], MAT_F_DONT_COPY_DATA);
          Mat_VarSetCell(cell_array, index, cell_element[index]);

          index               = 7 * i + 3;
          cell_element[index] = Mat_VarCreate(nullptr, MAT_C_INT32, MAT_T_INT32, 2, dims,
                                              &side_list[side_off], MAT_F_DONT_COPY_DATA);
          Mat_VarSetCell(cell_array, index, cell_element[index]);

          ex_get_side_set_node_list(exo_file, ids[i], &num_nodes_per_side[side_off],
                                    &side_nodes[node_off]);

          /* number-of-nodes-per-side list */
          index               = 7 * i + 4;
          cell_element[index] = Mat_VarCreate(nullptr, MAT_C_INT32, MAT_T_INT32, 2, dims,
                                              &num_nodes_per_side[side_off], MAT_F_DONT_COPY_DATA);
          Mat_VarSetCell(cell_array, index, cell_element[index]);

          dims[0]             = num_sideset_nodes[i];
          dims[1]             = 1;
          index               = 7 * i + 5;
          cell_element[index] = Mat_VarCreate(nullptr, MAT_C_INT32, MAT_T_INT32, 2, dims,
                                              &side_nodes[node_off], MAT_F_DONT_COPY_DATA);
          Mat_VarSetCell(cell_array, index, cell_element[index]);

          /* distribution-factors list */
          if (has_ss_dfac) {
            ex_get_set_dist_fact(exo_file, EX_SIDE_SET, ids[i], &ssdfac[df_off]);
          }
          else {
            n2 = num_sideset_dfac[i];
            for (int j = 0; j < n2; j++) {
              ssdfac[j] = 1.0;
            }
          }
          dims[0]             = num_sideset_dfac[i];
          dims[1]             = 1;
          index               = 7 * i + 6;
          cell_element[index] = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims,
                                              &ssdfac[df_off], MAT_F_DONT_COPY_DATA);
          Mat_VarSetCell(cell_array, index, cell_element[index]);

          side_off += num_sideset_sides[i];
          node_off += num_sideset_nodes[i];
          df_off += num_sideset_dfac[i];
        }
        Mat_VarWrite(mat_file, cell_array, MAT_COMPRESSION_NONE);
        Mat_VarFree(cell_array);
      }
      else {
        PutInt("ssids", num_sets, 1, Data(ids));
        std::vector<int>    elem_list;
        std::vector<int>    side_list;
        std::vector<int>    num_nodes_per_side;
        std::vector<int>    side_nodes;
        std::vector<double> ssdfac;
        for (int i = 0; i < num_sets; i++) {
          int n1;
          int n2;
          ex_get_set_param(exo_file, EX_SIDE_SET, ids[i], &n1, &n2);
          num_sideset_sides[i] = n1;
          num_sideset_dfac[i]  = n2;

          bool has_ss_dfac = (n2 != 0);
          if (n2 == 0 || n1 == n2) {
            ex_get_side_set_node_list_len(exo_file, ids[i], &n2);
          }

          num_nodes_per_side.resize(n1);
          side_nodes.resize(n2);
          ex_get_side_set_node_list(exo_file, ids[i], Data(num_nodes_per_side), Data(side_nodes));

          /* number-of-nodes-per-side list */
          std::string str;
          str = fmt::sprintf("ssnum%02d", i + 1);
          PutInt(str, n1, 1, Data(num_nodes_per_side));
          /* nodes list */
          str = fmt::sprintf("ssnod%02d", i + 1);
          PutInt(str, n2, 1, Data(side_nodes));

          /* distribution-factors list */
          if (has_ss_dfac) {
            ssdfac.resize(n2);
            ex_get_set_dist_fact(exo_file, EX_SIDE_SET, ids[i], Data(ssdfac));
            str = fmt::sprintf("ssfac%02d", i + 1);
            PutDbl(str, n2, 1, Data(ssdfac));
          }

          /* element and side list for side sets (dgriffi) */
          elem_list.resize(n1);
          side_list.resize(n1);
          ex_get_set(exo_file, EX_SIDE_SET, ids[i], Data(elem_list), Data(side_list));
          str = fmt::sprintf("ssside%02d", i + 1);
          PutInt(str, n1, 1, Data(side_list));
          str = fmt::sprintf("sselem%02d", i + 1);
          PutInt(str, n1, 1, Data(elem_list));
        }
      }
      get_put_user_names(exo_file, EX_SIDE_SET, num_sets, "ssusernames");

      /* Store # sides and # dis. factors per side set (dgriffi) */
      PutInt("nsssides", num_sets, 1, Data(num_sideset_sides));
      PutInt("nssdfac", num_sets, 1, Data(num_sideset_dfac));
    }
    return num_sideset_sides;
  }

  void handle_coordinates(int exo_file, size_t num_nodes, int num_axes)
  {
    if (debug) {
      logger("Coordinates");
    }
    std::vector<double> x, y, z;
    x.resize(num_nodes);
    if (num_axes >= 2) {
      y.resize(num_nodes);
    }
    if (num_axes == 3) {
      z.resize(num_nodes);
    }
    ex_get_coord(exo_file, Data(x), Data(y), Data(z));
    PutDbl("x0", num_nodes, 1, Data(x));
    if (num_axes >= 2) {
      PutDbl("y0", num_nodes, 1, Data(y));
    }
    if (num_axes == 3) {
      PutDbl("z0", num_nodes, 1, Data(z));
    }
  }

  /**********************************************************************/
  /* remove an argument from the list */
  void del_arg(int *argc, char *argv[], int j)
  {
    for (int jj = j + 1; jj < *argc; jj++) {
      argv[jj - 1] = argv[jj];
    }
    (*argc)--;
    argv[*argc] = nullptr;
  }
} // namespace

/**********************************************************************/
int main(int argc, char *argv[])
{
  std::string oname{};
  std::string filename{};

  std::string str;

  std::string ext = EXT;

  int err;
  int num_axes;
  int num_blocks;
  int num_side_sets;
  int num_node_sets;
  int num_global_vars;
  int num_nodal_vars;
  int num_element_vars;
  int num_nodeset_vars;
  int num_sideset_vars;

  size_t num_nodes    = 0;
  size_t num_elements = 0;

  enum mat_ft mat_version     = MAT_FT_DEFAULT;
  bool        use_cell_arrays = false;

  int cs_index = 0;

  /* process arguments */
  for (int j = 1; j < argc && argv[j][0] == '-'; j++) {
    if (strcmp(argv[j], "-t") == 0) { /* write text file (*.m) */
      del_arg(&argc, argv, j);
      textfile = 1;
      j--;
      continue;
    }
    if (strcmp(argv[j], "-h") == 0 || strcmp(argv[j], "--help") == 0) { /* write help info */
      del_arg(&argc, argv, j);
      usage();
      exit(1);
    }
    if (strcmp(argv[j], "-d") == 0) { /* write help info */
      del_arg(&argc, argv, j);
      j--;
      debug = true;
      continue;
    }
    if (strcmp(argv[j], "-i") == 0) { /* do not write info records */
      del_arg(&argc, argv, j);
      j--;
      do_info = false;
      continue;
    }
    if (strcmp(argv[j], "-c") == 0) { /* use cell arrays */
      del_arg(&argc, argv, j);
      j--;
      use_cell_arrays = true;
      continue;
    }
    if (strcmp(argv[j], "-v73") == 0) { /* Version 7.3 */
      del_arg(&argc, argv, j);
      mat_version = MAT_FT_MAT73;
      j--;
      continue;
    }
    // This matches the option used in matlab
    if ((strcmp(argv[j], "-v7.3") == 0) || (strcmp(argv[j], "-V7.3") == 0)) { /* Version 7.3 */
      del_arg(&argc, argv, j);
      mat_version = MAT_FT_MAT73;
      j--;
      continue;
    }
    if (strcmp(argv[j], "-v5") == 0) { /* Version 5 (default) */
      del_arg(&argc, argv, j);
      mat_version = MAT_FT_MAT5;
      j--;
      continue;
    }
    if (strcmp(argv[j], "-o") == 0) { /* specify output file name */
      del_arg(&argc, argv, j);
      if (argv[j] != nullptr) {
        oname = argv[j];
        del_arg(&argc, argv, j);
        fmt::print("output file: {}\n", oname);
      }
      else {
        fmt::print(stderr, "ERROR: Invalid output file specification.\n");
        return 2;
      }
      j--;
      continue;
    }
    if (strcmp(argv[j], "-change_set") == 0) {
      del_arg(&argc, argv, j);
      if (argv[j] != nullptr) {
        cs_index = std::stoi(argv[j]);
        del_arg(&argc, argv, j);
      }
      else {
        fmt::print(stderr, "ERROR: Invalid output file specification.\n");
        return 2;
      }
      j--;
      continue;
    }
    // Unrecognized option...
    fmt::print(stderr, "ERROR: Unrecognized option: '{}'\n", argv[j]);
    usage();
    exit(1);
  }

  /* QA Info */
  fmt::print("{}: {}, {}\n", qainfo[0], qainfo[2], qainfo[1]);

  /* usage message*/
  if (argc != 2) {
    usage();
    exit(1);
  }

  /* open output file */
  if (textfile != 0) {
    ext = ".m";
  }
  if (oname.empty()) {
    filename   = argv[1];
    size_t pos = filename.find_last_of('.');
    if (pos != std::string::npos) {
      filename = filename.substr(0, pos);
    }
    filename += ext;
  }
  else {
    filename = oname;
  }

  if (textfile != 0) {
    m_file = fopen(filename.c_str(), "w");
    if (m_file == nullptr) {
      fmt::print(stderr, "ERROR: Unable to open '{}'\n", filename);
      exit(1);
    }
  }
  else {
    mat_file = Mat_CreateVer(filename.c_str(), nullptr, mat_version);
    if (mat_file == nullptr) {
      fmt::print(stderr, "ERROR: Unable to create matlab file '{}'\n", filename);
      exit(1);
    }
  }

  ex_opts(EX_VERBOSE);

  /* word sizes */
  int cpu_word_size = sizeof(double);
  int io_word_size  = 0;

  /* open exodus file */
  float exo_version;
  int   exo_file = ex_open(argv[1], EX_READ, &cpu_word_size, &io_word_size, &exo_version);
  if (exo_file < 0) {
    fmt::print(stderr, "ERROR: Cannot open '{}'\n", argv[1]);
    exit(1);
  }
  // See if file has change sets...
  int num_change_sets = ex_inquire_int(exo_file, EX_INQ_NUM_CHILD_GROUPS);
  if (num_change_sets > 0) {
    if (cs_index == 0) {
      fmt::print(stderr,
                 "WARNING: Exodus database contains {} change sets.\n         Setting to read from "
                 "first change set since `-change_set #` option not specified.\n\n",
                 num_change_sets);
      exo_file++;
    }
    else if (cs_index > num_change_sets) {
      fmt::print(stderr,
                 "ERROR: The selected change set ({}) exceeds the number of change sets ({}) on "
                 "the exodus database.\n",
                 cs_index, num_change_sets);
      exit(1);
    }
    else {
      // Set to selected change set
      fmt::print(stderr, "NOTE: Exodus data will be read from change set {}.\n\n", cs_index);
      exo_file += cs_index;
    }
  }
  else if (cs_index > 0) {
    // Error, selected change set, but there are none.
    fmt::print(stderr,
               "ERROR: Change set {} was selected, but the exodus database does not contain change "
               "sets.\n",
               cs_index, num_change_sets);
    exit(1);
  }

  /* print */
  fmt::print("\ttranslating {} to {}...\n", argv[1], filename);

  /* read database parameters */
  std::array<char, MAX_LINE_LENGTH + 1> line;
  ex_get_init(exo_file, line.data(), &num_axes, &num_nodes, &num_elements, &num_blocks,
              &num_node_sets, &num_side_sets);
  int num_info_lines = ex_inquire_int(exo_file, EX_INQ_INFO);
  int num_time_steps = ex_inquire_int(exo_file, EX_INQ_TIME);
  ex_get_variable_param(exo_file, EX_GLOBAL, &num_global_vars);
  ex_get_variable_param(exo_file, EX_NODAL, &num_nodal_vars);
  ex_get_variable_param(exo_file, EX_ELEM_BLOCK, &num_element_vars);
  ex_get_variable_param(exo_file, EX_NODE_SET, &num_nodeset_vars);
  ex_get_variable_param(exo_file, EX_SIDE_SET, &num_sideset_vars);

  /* export parameters */
  PutInt("naxes", num_axes);
  PutInt("nnodes", num_nodes);
  PutInt("nelems", num_elements);
  PutInt("nblks", num_blocks);
  PutInt("nnsets", num_node_sets);
  PutInt("nssets", num_side_sets);
  PutInt("nsteps", num_time_steps);
  PutInt("ngvars", num_global_vars);
  PutInt("nnvars", num_nodal_vars);
  PutInt("nevars", num_element_vars);
  PutInt("nnsvars", num_nodeset_vars);
  PutInt("nssvars", num_sideset_vars);

  /* allocate -char- scratch space*/
  int nstr2   = do_info ? num_info_lines : 0;
  nstr2       = std::max(nstr2, num_blocks);
  nstr2       = std::max(nstr2, num_node_sets);
  nstr2       = std::max(nstr2, num_side_sets);
  char **str2 = get_exodus_names(nstr2, 512);

  /* title */
  PutStr("Title", line.data());

  /* information records */
  if (do_info && num_info_lines > 0) {
    ex_get_info(exo_file, str2);
    std::string ostr;
    for (int i = 0; i < num_info_lines; i++) {
      if (std::strlen(str2[i]) > 0) {
        ostr += str2[i];
        ostr += "\n";
      }
    }
    PutStr("info", ostr);
    ostr = "";
    for (int i = 0; i < num_info_lines; i++) {
      if (std::strlen(str2[i]) > 0 && strncmp(str2[i], "cavi", 4) == 0) {
        ostr += str2[i];
        ostr += "\n";
      }
    }
    PutStr("cvxp", ostr);
  }

  /* nodal coordinates */
  handle_coordinates(exo_file, num_nodes, num_axes);

  /* side sets */
  if (debug) {
    logger("Side Sets");
  }
  auto num_sideset_sides = handle_side_sets(exo_file, num_side_sets, use_cell_arrays);

  /* node sets (section by dgriffi) */
  auto num_nodeset_nodes = handle_node_sets(exo_file, num_node_sets, use_cell_arrays);

  /* element blocks */
  if (debug) {
    logger("Element Blocks");
  }
  auto num_elem_in_block = handle_element_blocks(exo_file, num_blocks, use_cell_arrays);

  /* time values */
  if (num_time_steps > 0) {
    if (debug) {
      logger("Time Steps");
    }
    std::vector<double> scr(num_time_steps);
    ex_get_all_times(exo_file, Data(scr));
    PutDbl("time", num_time_steps, 1, Data(scr));
  }

  /* global variables */
  if (num_global_vars > 0) {
    if (debug) {
      logger("Global Variables");
    }

    if (use_cell_arrays) {
      size_t dims[2];
      dims[0]              = 2;
      dims[1]              = num_global_vars;
      matvar_t *cell_array = Mat_VarCreate("gvar", MAT_C_CELL, MAT_T_CELL, 2, dims, nullptr, 0);
      assert(cell_array);
      std::vector<double> scr(num_time_steps * num_global_vars);
      dims[0]       = num_time_steps;
      dims[1]       = 1;
      size_t offset = 0;
      // Get vector of variable names...
      auto gnames = get_names(exo_file, EX_GLOBAL, num_global_vars);

      std::vector<matvar_t *> cell_element(num_global_vars * 2);
      int                     j = 0;
      for (int i = 0; i < num_global_vars; i++) {
        size_t sdims[2];
        sdims[0]        = 1;
        sdims[1]        = gnames[i].length();
        cell_element[j] = Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8, 2, sdims,
                                        (void *)gnames[i].c_str(), MAT_F_DONT_COPY_DATA);
        Mat_VarSetCell(cell_array, j, cell_element[j]);
        j++;

        ex_get_var_time(exo_file, EX_GLOBAL, i + 1, 1, 1, num_time_steps, &scr[offset]);
        cell_element[j] = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims, &scr[offset],
                                        MAT_F_DONT_COPY_DATA);
        assert(cell_element[j]);
        Mat_VarSetCell(cell_array, j, cell_element[j]);
        offset += num_time_steps;
        j++;
      }
      Mat_VarWrite(mat_file, cell_array, MAT_COMPRESSION_NONE);
      Mat_VarFree(cell_array);
    }
    else {
      get_put_names(exo_file, EX_GLOBAL, num_global_vars, "gnames");
      std::vector<double> scr(num_time_steps);
      for (int i = 0; i < num_global_vars; i++) {
        str = fmt::sprintf("gvar%02d", i + 1);
        ex_get_var_time(exo_file, EX_GLOBAL, i + 1, 1, 1, num_time_steps, Data(scr));
        PutDbl(str, num_time_steps, 1, Data(scr));
      }
    }
  }

  /* nodal variables */
  if (num_nodal_vars > 0) {
    if (debug) {
      logger("Nodal Variables");
    }
    if (debug) {
      logger("\tNames");
    }
    if (use_cell_arrays) {
      size_t dims[2];
      dims[0]              = 2;
      dims[1]              = num_nodal_vars;
      matvar_t *cell_array = Mat_VarCreate("nvar", MAT_C_CELL, MAT_T_CELL, 2, dims, nullptr, 0);
      assert(cell_array);
      std::vector<double> scr(static_cast<size_t>(num_nodal_vars) * num_time_steps * num_nodes);
      dims[0]       = num_nodes;
      dims[1]       = num_time_steps;
      size_t offset = 0;
      // Get vector of variable names...
      auto nnames = get_names(exo_file, EX_NODAL, num_nodal_vars);

      std::vector<matvar_t *> cell_element(num_nodal_vars * 2);
      int                     j = 0;
      for (int i = 0; i < num_nodal_vars; i++) {
        size_t sdims[2];
        sdims[0]        = 1;
        sdims[1]        = nnames[i].length();
        cell_element[j] = Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8, 2, sdims,
                                        (void *)nnames[i].c_str(), MAT_F_DONT_COPY_DATA);
        Mat_VarSetCell(cell_array, j, cell_element[j]);
        j++;

        cell_element[j] = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims, &scr[offset],
                                        MAT_F_DONT_COPY_DATA);
        assert(cell_element[j]);
        Mat_VarSetCell(cell_array, j, cell_element[j]);
        for (int k = 0; k < num_time_steps; k++) {
          ex_get_var(exo_file, k + 1, EX_NODAL, i + 1, 1, num_nodes, &scr[num_nodes * k + offset]);
        }
        offset += num_time_steps * num_nodes;
        j++;
      }
      Mat_VarWrite(mat_file, cell_array, MAT_COMPRESSION_NONE);
      Mat_VarFree(cell_array);
    }
    else {
      get_put_names(exo_file, EX_NODAL, num_nodal_vars, "nnames");

      std::vector<double> scr(num_nodes * num_time_steps);
      for (int i = 0; i < num_nodal_vars; i++) {
        str = fmt::sprintf("nvar%02d", i + 1);
        if (debug) {
          logger("\tReading");
        }
        for (int j = 0; j < num_time_steps; j++) {
          ex_get_var(exo_file, j + 1, EX_NODAL, i + 1, 1, num_nodes, &scr[num_nodes * j]);
        }
        if (debug) {
          logger("\tWriting");
        }
        PutDbl(str, num_nodes, num_time_steps, Data(scr));
      }
    }
  }

  /* element variables */
  if (num_element_vars > 0) {
    if (debug) {
      logger("Element Variables");
    }
    get_put_vars(exo_file, EX_ELEM_BLOCK, num_blocks, num_element_vars, num_time_steps,
                 num_elem_in_block, "e", use_cell_arrays);
  }

  /* nodeset variables */
  if (num_nodeset_vars > 0) {
    if (debug) {
      logger("Nodeset Variables");
    }
    get_put_vars(exo_file, EX_NODE_SET, num_node_sets, num_nodeset_vars, num_time_steps,
                 num_nodeset_nodes, "ns", use_cell_arrays);
  }

  /* sideset variables */
  if (num_sideset_vars > 0) {
    if (debug) {
      logger("Sideset Variables");
    }
    get_put_vars(exo_file, EX_SIDE_SET, num_side_sets, num_sideset_vars, num_time_steps,
                 num_sideset_sides, "ss", use_cell_arrays);
  }

  /* node and element number maps */
  if (debug) {
    logger("Node and Element Number Maps");
  }
  ex_opts(0); /* turn off error reporting. It is not an error to have no map*/
  std::vector<int> ids(num_nodes);
  err = ex_get_id_map(exo_file, EX_NODE_MAP, Data(ids));
  if (err == 0) {
    PutInt("node_num_map", num_nodes, 1, Data(ids));
  }

  ids.resize(num_elements);
  err = ex_get_id_map(exo_file, EX_ELEM_MAP, Data(ids));
  if (err == 0) {
    PutInt("elem_num_map", num_elements, 1, Data(ids));
  }

  if (debug) {
    logger("Closing file");
  }
  ex_close(exo_file);

  if (textfile != 0) {
    fclose(m_file);
  }
  else {
    Mat_Close(mat_file);
  }

  delete_exodus_names(str2, nstr2);

  fmt::print("done...\n");

  /* exit status */
  add_to_log("exo2mat", 0);
  return (0);
}
