/*
 * Copyright(C) 1999-2021 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
 */

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *----------------------------------------------------------------------------
 * Functions contained in this file:
 *      cmd_line_arg_parse()
 *      read_cmd_file()
 *      check_inp_specs()
 *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
#include "copy_string_cpp.h"
#include "elb.h"     // for Problem_Description, etc
#include "elb_err.h" // for Gen_Error, error_lev
#include "elb_inp.h"
#include "elb_util.h" // for strip_string, token_compare, etc
#include "fmt/ostream.h"
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ||                \
    defined(__MINGW32__) || defined(_WIN64) || defined(__MINGW64__)
#include "XGetopt.h"
#include <unistd.h>
#else
#include "getopt.h" // for getopt
#endif
#include "scopeguard.h"
#include <cstddef> // for size_t
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500
#endif
#include <cstdlib>    // for malloc, exit, free
#include <cstring>    // for strcmp, strstr, strchr, etc
#include <exodusII.h> // for ex_close, EX_READ, etc

namespace {
  void print_usage();

  std::string remove_extension(const std::string &filename)
  {
    // Strip off the extension
    size_t ind = filename.find_last_of('.', filename.size());
    if (ind != std::string::npos) {
      return filename.substr(0, ind);
    }
    return filename;
  }
} // namespace
/*****************************************************************************/
/*****************************************************************************/
/*****************************************************************************/
/* This function parses the command line options and stores the information
 * in the appropriate data locations.
 *---------------------------------------------------------------------------*/
template int cmd_line_arg_parse(int argc, char *argv[], std::string &exoII_inp_file,
                                std::string &ascii_inp_file, std::string &nemI_out_file,
                                Machine_Description *machine, LB_Description<int> *lb,
                                Problem_Description *prob, Solver_Description *solver,
                                Weight_Description<int> *weight);

template int cmd_line_arg_parse(int argc, char *argv[], std::string &exoII_inp_file,
                                std::string &ascii_inp_file, std::string &nemI_out_file,
                                Machine_Description *machine, LB_Description<int64_t> *lb,
                                Problem_Description *prob, Solver_Description *solver,
                                Weight_Description<int64_t> *weight);

template <typename INT>
int cmd_line_arg_parse(int argc, char *argv[],                  /* Args as passed by main() */
                       std::string             &exoII_inp_file, /* The input ExodusII file name */
                       std::string             &ascii_inp_file, /* The ASCII input file name */
                       std::string             &nemI_out_file,  /* Output NemesisI file name */
                       Machine_Description     *machine, /* Structure for machine description */
                       LB_Description<INT>     *lb,     /* Structure for load balance description */
                       Problem_Description     *prob,   /* Structure for various problem params */
                       Solver_Description      *solver, /* Structure for eigen solver params */
                       Weight_Description<INT> *weight  /* Structure for weighting graph */
)
{
  int         opt_let;
  int         iret;
  int         el_blk;
  int         wgt;
  int         max_dim = 0;
  int         i;
  char       *sub_opt = nullptr, *value = nullptr, *cptr = nullptr, *cptr2 = nullptr;
  std::string ctemp;

  /* see NOTE in elb.h about the order of the following array */
  const char *weight_subopts[] = {"none",  "read",       "eb",       "var_index",
                                  "edges", "time_index", "var_name", nullptr};

  const char *mach_subopts[] = {"mesh", "hcube", "hypercube", "cluster", nullptr};

  const char *lb_subopts[] = {"multikl",   "spectral", "inertial", "linear", "random",
                              "scattered", "infile",   "kl",       "none",   "num_sects",
                              "cnctd_dom", "outfile",  "zpinch",   "brick",  "rcb",
                              "rib",       "hsfc",     "ignore_z", nullptr};

  const char *solve_subopts[] = {"tolerance", "use_rqi", "vmax", nullptr};

  /*---------------------------Execution Begins--------------------------------*/

  /*
   * Make sure there were command line options given. If not assign the
   * name of the default ascii input file.
   */
  if (argc <= 1) {
    print_usage();
    exit(0);
  }

  /* Loop over each command line option */
  while ((opt_let = getopt(argc, argv, "3264a:hm:l:nes:x:w:vyo:cg:fpS")) != EOF) {

    /* case over the option letter */
    switch (opt_let) {
    case 'v':
      /* Should an output visualization file be output */
      prob->vis_out = 1;
      break;

    case 'y':
      /* Should an output visualization file be output */
      prob->vis_out = 2;
      break;

    case 'x':
      /* Undocumented flag for setting the error level */
      if (optarg == nullptr) {
        error_lev = 1;
      }
      else {
        iret = sscanf(optarg, "%d", &error_lev);
        if (iret != 1) {
          error_lev = 1;
        }

        if (error_lev > 3) {
          error_lev = 3;
        }
      }
      break;

    case 'c':
      /* flag to allow the user to skip some error checks */
      prob->skip_checks = 1;
      break;

    case 'f':
      /*
       * use the face method to calculate adjacencies for
       * elemental decompositions
       */
      prob->face_adj = 1;
      break;

    case 'C':
      /*
       * detect vertical columns of elements and ensure that
       * elements of a columns are all in one partition
       */
      prob->fix_columns = 1;
      break;

    case 'p':
      /*
       * use the partial method to determine adjacencies:
       * only 3/4 of face nodes must match instead of all
       */
      prob->partial_adj = 1;
      break;

    case 'w':
      /* Weighting options */
      sub_opt = optarg;
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ||                \
    defined(__MINGW32__) || defined(_WIN64) || defined(__MINGW64__)
      fmt::print(stderr, "Windows build does not use getsubopt yet...\n");
      exit(1);
#else
      while (sub_opt != nullptr && *sub_opt != '\0') {
        switch (getsubopt(&sub_opt, (char *const *)weight_subopts, &value)) {
        case READ_EXO:
          if (value == nullptr) {
            ctemp =
                fmt::format("FATAL: must specify a file name with {}", weight_subopts[READ_EXO]);
            Gen_Error(0, ctemp);
            return 0;
          }
          if (strlen(value) == 0) {
            ctemp =
                fmt::format("FATAL: must specify a file name with {}", weight_subopts[READ_EXO]);
            Gen_Error(0, ctemp);
            return 0;
          }

          if (weight->type < 0) {
            weight->type = READ_EXO;
          }
          else if (!(weight->type & READ_EXO)) {
            weight->type += READ_EXO;
          }

          /* check if the read is after an element block weight */
          if (weight->type & EL_BLK) {
            weight->ow_read = 0;
          }

          weight->exo_filename = value;

          break; /* End "case READ_EXO" */

        case VAR_INDX:
          if (value == nullptr) {
            ctemp = fmt::format("FATAL: must specify a value with {}", weight_subopts[VAR_INDX]);
            Gen_Error(0, ctemp);
            return 0;
          }
          if (strlen(value) == 0) {
            ctemp = fmt::format("FATAL: must specify a value with {}", weight_subopts[VAR_INDX]);
            Gen_Error(0, ctemp);
            return 0;
          }

          iret = sscanf(value, "%d", &(weight->exo_vindx));
          if (iret != 1) {
            ctemp = fmt::format("FATAL: invalid value specified for {}", weight_subopts[VAR_INDX]);
            Gen_Error(0, ctemp);
            return 0;
          }
          break;

        case TIME_INDX:
          if (value == nullptr) {
            ctemp = fmt::format("FATAL: must specify a value with {}", weight_subopts[TIME_INDX]);
            Gen_Error(0, ctemp);
            return 0;
          }
          if (strlen(value) == 0) {
            ctemp = fmt::format("FATAL: must specify a value with {}", weight_subopts[TIME_INDX]);
            Gen_Error(0, ctemp);
            return 0;
          }

          iret = sscanf(value, "%d", &(weight->exo_tindx));
          if (iret != 1) {
            ctemp = fmt::format("FATAL: invalid value specified for {}", weight_subopts[TIME_INDX]);
            Gen_Error(0, ctemp);
            return 0;
          }
          break;

        case VAR_NAME:
          if (value == nullptr) {
            ctemp = fmt::format("FATAL: must specify a value with {}", weight_subopts[VAR_NAME]);
            Gen_Error(0, ctemp);
            return 0;
          }
          if (strlen(value) == 0) {
            ctemp = fmt::format("FATAL: must specify a value with {}", weight_subopts[VAR_NAME]);
            Gen_Error(0, ctemp);
            return 0;
          }

          weight->exo_varname = value;

          break;

        case EL_BLK:
          if (value == nullptr) {
            ctemp = fmt::format("FATAL: must specify an element block and weight with {}",
                                weight_subopts[EL_BLK]);
            Gen_Error(0, ctemp);
            return 0;
          }
          el_blk = -1;
          wgt    = -1;

          iret = sscanf(value, "%d:%d", &el_blk, &wgt);
          if (iret != 2) {
            Gen_Error(0, "invalid element block weight");
            return 0;
          }
          if (el_blk <= 0) {
            ctemp = fmt::format("invalid element block, %d", el_blk);
            Gen_Error(0, ctemp);
            return 0;
          }
          if (wgt < 0) {
            ctemp = fmt::format("invalid weight, %d", wgt);
            Gen_Error(0, ctemp);
            return 0;
          }

          weight->elemblk.push_back(el_blk);
          weight->elemblk_wgt.push_back(wgt);

          if (weight->type < 0) {
            weight->type = EL_BLK;
          }
          else if (!(weight->type & EL_BLK)) {
            weight->type += EL_BLK;
          }

          /* check if the element block weight needs to over write the read */
          if (weight->type & READ_EXO) {
            weight->ow_read = 1;
          }

          break;

        case EDGE_WGT:
          if (weight->type < 0) {
            weight->type = EDGE_WGT;
          }
          else if (!(weight->type & EDGE_WGT)) {
            weight->type += EDGE_WGT;
          }
          break;

        case NO_WEIGHT: weight->type = NO_WEIGHT; break;

        default:
          ctemp = fmt::format("FATAL: unknown suboption {} specified", value);
          Gen_Error(0, ctemp);
          return 0;

        } /* End "switch(getsubopt(&sub_opt, weight_subopts, &value))" */

      } /* End "while(*sub_opt != '\0')" */
#endif
      break; /* End "case 'w'" */

    case 'a':
      /* Only an ASCII input file name */
      if (optarg != nullptr) {
        ascii_inp_file = optarg;
      }
      break;

    case 'o':
      /* Output NemesisI file name */
      if (optarg != nullptr) {
        nemI_out_file = optarg;
      }
      break;

    case 'n':
      /* Nodal decomposition */
      if (prob->type == ELEMENTAL) {
        Gen_Error(0, "FATAL: -e and -n are mutually exclusive");
        return 0;
      }
      prob->type = NODAL;
      break;

    case 'e':
      /* Elemental decomposition */
      if (prob->type == NODAL) {
        Gen_Error(0, "FATAL: -e and -n are mutually exclusive\n");
        return 0;
      }
      prob->type = ELEMENTAL;
      break;

    case 'm':
      /* Machine type */
      sub_opt = optarg;
      if (sub_opt != nullptr) {
        string_to_lower(sub_opt, '\0');
      }
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ||                \
    defined(__MINGW32__) || defined(_WIN64) || defined(__MINGW64__)
      fmt::print(stderr, "Windows build does not use getsubopt yet...\n");
      exit(1);
#else
      while (sub_opt != nullptr && *sub_opt != '\0') {

        /* Switch over the machine description */
        switch (getsubopt(&sub_opt, (char *const *)mach_subopts, &value)) {
        case HCUBE:
        case HYPERCUBE:
          if (machine->type < 0) {
            machine->type = HCUBE;
            max_dim       = 1;
          }
          FALL_THROUGH;

        case MESH:
          if (machine->type < 0) {
            machine->type = MESH;
            max_dim       = 3;
          }

          cptr = value; /* want to set this for both mesh and hcube */
          FALL_THROUGH;

        case CLUSTER:
          if (machine->type < 0) /* so, get the number of boxes */
          {
            if (value == nullptr || strlen(value) == 0) {
              Gen_Error(0, "FATAL: need to specify number of boxes");
              return 0;
            }

            /* now need to find what each box consists of */
            cptr = strpbrk(value, "mMhH");
            if (*cptr == 'm' || *cptr == 'M') {
              machine->type = MESH;
              max_dim       = 3;
            }
            else if (*cptr == 'h' || *cptr == 'H') {
              machine->type = HCUBE;
              max_dim       = 1;
            }
            else {
              Gen_Error(0, "FATAL: unknown type specified with cluster");
              return 0;
            }
            /* blank out character and move cptr to next char */
            *cptr = '\0';
            cptr++;

            /* get the number of boxes from value */
            iret = sscanf(value, "%d", &(machine->num_boxes));
            if (iret <= 0 || machine->num_boxes <= 0) {
              Gen_Error(0, "FATAL: invalid number of boxes");
              return 0;
            }
          }

          if (cptr == nullptr || strlen(cptr) == 0) {
            Gen_Error(0, "FATAL: need to specify dimension");
            return 0;
          }
          cptr2 = strtok(cptr, "xX");
          if (cptr2 == nullptr) {
            Gen_Error(0, "FATAL: bad size for dimension specification");
            return 0;
          }
          machine->num_dims = 0;
          for (i = 0; i < max_dim; i++) {
            machine->dim[i] = 1;
          }
          while (cptr2) {
            iret = sscanf(cptr2, "%d", &(machine->dim[machine->num_dims]));
            if (iret <= 0 || machine->dim[machine->num_dims] <= 0) {
              Gen_Error(0, "FATAL: invalid dimension specification");
              return 0;
            }

            machine->num_dims++;
            cptr2 = strtok(nullptr, "xX");

            /* Only up to three-dimensional allowed */
            if (machine->num_dims == max_dim && cptr2 != nullptr) {
              Gen_Error(0, "FATAL: maximum number of dimensions exceeded");
              return 0;
            }
          }

          break; /* End "case MESH or HCUBE or CLUSTER" */

        default: Gen_Error(0, "FATAL: unknown machine type"); return 0;

        } /* End "switch(getsubopt(&sub_opt, mach_subopts, &value))" */

      } /* End "while(*sub_opt != '\0')" */
#endif
      break; /* End "case 'm'" */

    case 'l':
      /* Load balance information */
      sub_opt = optarg;
      if (sub_opt != nullptr) {
        string_to_lower(sub_opt, '\0');
      }
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ||                \
    defined(__MINGW32__) || defined(_WIN64) || defined(__MINGW64__)
      fmt::print(stderr, "Windows build does not use getsubopt yet...\n");
      exit(1);
#else
      while (sub_opt != nullptr && *sub_opt != '\0') {
        switch (getsubopt(&sub_opt, (char *const *)lb_subopts, &value)) {
        case MULTIKL: lb->type = MULTIKL; break;

        case SPECTRAL: lb->type = SPECTRAL; break;

        case INERTIAL: lb->type = INERTIAL; break;

        case ZPINCH: lb->type = ZPINCH; break;

        case BRICK: lb->type = BRICK; break;

        case ZOLTAN_RCB: lb->type = ZOLTAN_RCB; break;

        case ZOLTAN_RIB: lb->type = ZOLTAN_RIB; break;

        case ZOLTAN_HSFC: lb->type = ZOLTAN_HSFC; break;

        case LINEAR: lb->type = LINEAR; break;

        case RANDOM: lb->type = RANDOM; break;

        case SCATTERED: lb->type = SCATTERED; break;

        case INFILE:
          if (value == nullptr) {
            Gen_Error(0, "FATAL: need to specify a value with file");
            return 0;
          }
          char tmpstr[2048];
          iret     = sscanf(value, "%2047s", tmpstr);
          lb->file = tmpstr;
          if (iret != 1) {
            Gen_Error(0, "FATAL: invalid value associated with file");
            return 0;
          }
          lb->type = INFILE;
          break;

        case KL_REFINE: lb->refine = KL_REFINE; break;

        case NO_REFINE: lb->refine = NO_REFINE; break;

        case NUM_SECTS:
          if (value == nullptr) {
            Gen_Error(0, "FATAL: need to specify a value with num_sects");
            return 0;
          }
          iret = sscanf(value, "%d", &(lb->num_sects));
          if (iret != 1) {
            Gen_Error(0, "FATAL: invalid value associated with num_sects");
            return 0;
          }
          break;

        case CNCT_DOM: lb->cnctd_dom = 1; break;

        case IGNORE_Z: lb->ignore_z = 1; break;

        case OUTFILE:
          if (value == nullptr) {
            Gen_Error(0, "FATAL: need to specify a value with outfile");
            return 0;
          }
          iret     = sscanf(value, "%2047s", tmpstr);
          lb->file = tmpstr;
          if (iret != 1) {
            Gen_Error(0, "FATAL: invalid value associated with outfile");
            return 0;
          }
          lb->outfile = ELB_TRUE;
          break;

        default:
          ctemp = fmt::format("FATAL: unknown lb param \"{}\"", value);
          Gen_Error(0, ctemp);
          return 0;

        } /* End "switch(getsubopt(&sup_opt, mach_subopts, &value))" */

      } /* End "while(*sup_opt != '\0')" */
#endif
      break; /* End "case 'l'" */

    case 'S': prob->no_sph = 1; break;

    case 's':
      /* Eigen solver options */
      sub_opt = optarg;
      if (sub_opt != nullptr) {
        string_to_lower(sub_opt, '\0');
      }
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ||                \
    defined(__MINGW32__) || defined(_WIN64) || defined(__MINGW64__)
      fmt::print(stderr, "Windows build does not use getsubopt yet...\n");
      exit(1);
#else
      while (sub_opt != nullptr && *sub_opt != '\0') {
        switch (getsubopt(&sub_opt, (char *const *)solve_subopts, &value)) {
        case TOLER:
          if (value == nullptr) {
            fmt::print(stderr, "FATAL: tolerance specification requires \
value\n");
            return 0;
          }
          iret = sscanf(value, "%le", &(solver->tolerance));
          if (iret != 1) {
            fmt::print(stderr, "FATAL: incorrect value for tolerance\n");
            return 0;
          }

          break;

        case USE_RQI:
          if (solver->rqi_flag == USE_RQI) {
            solver->rqi_flag = -1;
          }
          else {
            solver->rqi_flag = USE_RQI;
          }

          break;

        case VMAX:
          if (value == nullptr) {
            fmt::print(stderr, "FATAL: must specify a value with {}\n", solve_subopts[VMAX]);
            return 0;
          }
          iret = sscanf(value, "%d", &(solver->vmax));
          if (iret != 1) {
            fmt::print(stderr, "FATAL: invalid value read for {}\n", solve_subopts[VMAX]);
            return 0;
          }

          break;

        default: fmt::print(stderr, "FATAL: unknown solver option\n"); return 0;

        } /* End "switch(getsubopt(&sub_opt, solve_subopts, &value))" */

      } /* End "while(sub_opt != '\0')" */
#endif
      break; /* End "case 's'" */

    case 'g':
      /* group designations */
      /* allocate string to hold designation */
      if (optarg != nullptr) {
        prob->groups = reinterpret_cast<char *>(malloc(strlen(optarg) + 1));
        copy_string(prob->groups, optarg, strlen(optarg) + 1);
      }
      break;

    case 'h':
      /* Usage info was requested */
      print_usage();
      exit(0);

    case '6':
    case '4':
      /* ignore -- used to parse -64 option */
      break;

    case '3':
    case '2':
      /* ignore -- used to parse -32 option */
      break;

    default:
      /* Default case. Error on unknown argument. */
      return 0;

    } /* End "switch(opt_let)" */

  } /* End "while((opt_let=getopt(argc, argv, "i")) != EOF)" */
  /* Get the input file name, if specified on the command line */
  if ((argc - optind) >= 1) {
    exoII_inp_file = argv[optind];
  }
  else {
    exoII_inp_file = "";
  }
  return 1;

} /*-------End cmd_line_arg_parse()--------*/

/*****************************************************************************/
/*****************************************************************************/
/*****************************************************************************/
/* This function reads in the ASCII command file.
 *---------------------------------------------------------------------------*/
template int read_cmd_file(std::string &ascii_inp_file, std::string &exoII_inp_file,
                           std::string &nemI_out_file, Machine_Description *machine,
                           LB_Description<int> *lb, Problem_Description *problem,
                           Solver_Description *solver, Weight_Description<int> *weight);
template int read_cmd_file(std::string &ascii_inp_file, std::string &exoII_inp_file,
                           std::string &nemI_out_file, Machine_Description *machine,
                           LB_Description<int64_t> *lb, Problem_Description *problem,
                           Solver_Description *solver, Weight_Description<int64_t> *weight);

template <typename INT>
int read_cmd_file(std::string &ascii_inp_file, std::string &exoII_inp_file,
                  std::string &nemI_out_file, Machine_Description *machine, LB_Description<INT> *lb,
                  Problem_Description *problem, Solver_Description *solver,
                  Weight_Description<INT> *weight)
{
  FILE       *inp_fd;
  std::string ctemp;
  char        inp_line[MAX_INP_LINE];
  char        inp_copy[MAX_INP_LINE];
  char       *cptr, *cptr2;

  int  iret;
  int  el_blk;
  int  wgt;
  int  i;
  int  ilen;
  int  max_dim;
  char tmpstr[2048];
  /*-----------------------------Execution Begins------------------------------*/
  if (!(inp_fd = fopen(ascii_inp_file.c_str(), "r"))) {
    ctemp = fmt::format("FATAL: unable to open ASCII input file {}", ascii_inp_file);
    Gen_Error(0, ctemp);
    return 0;
  }
  ON_BLOCK_EXIT(fclose, inp_fd);
  /* Begin parsing the input file */
  while (fgets(inp_line, MAX_INP_LINE, inp_fd)) {
    if (inp_line[0] != '#') {
      copy_string(inp_copy, inp_line);
      clean_string(inp_line, " \t");
      cptr = strtok(inp_line, "\t=");
      if (token_compare(cptr, "input exodusii file")) {
        /* The input ExodusII file name */
        if (exoII_inp_file.empty()) {
          cptr = strtok(nullptr, "\t=");
          strip_string(cptr, " \t\n");
          exoII_inp_file = cptr;
        }
      }
      else if (token_compare(cptr, "output visualization file")) {
        if (problem->vis_out < 0) {
          /* Output a visualization file */
          cptr = strtok(nullptr, "\t=");
          strip_string(cptr, " \t\n");
          if (strcasecmp(cptr, "yes") == 0 || strcasecmp(cptr, "true") == 0) {
            problem->vis_out = 1;
          }
          else if (strcasecmp(cptr, "no") == 0 || strcasecmp(cptr, "false") == 0) {
            problem->vis_out = 0;
          }
          else {
            iret = sscanf(cptr, "%d", &problem->vis_out);
            if (iret != 1) {
              Gen_Error(1, "WARNING: unknown visualization output flag");
              problem->vis_out = 0;
            }
            else {
              if (problem->vis_out != 1 && problem->vis_out != 2) {
                problem->vis_out = 0;
              }
            }
          }
        }
      }
      else if (token_compare(cptr, "output nemesisi file")) {
        /* The NemesisI output file name */
        if (nemI_out_file.empty()) {
          cptr = strtok(nullptr, "\t=");
          strip_string(cptr, " \t\n");
          nemI_out_file = cptr;
        }
      }
      else if (token_compare(cptr, "decomposition method")) {
        /* The method to use for decomposing the graph */
        if (lb->type < 0 || lb->refine < 0 || lb->num_sects < 0) {

          /* Search to the first null character */
          cptr = strchr(cptr, '\0');
          cptr++;
          strip_string(cptr, " \t\n=");
          cptr = strtok(cptr, ",");
          while (cptr != nullptr) {
            strip_string(cptr, " \t\n");
            string_to_lower(cptr, '\0');
            if (strcmp(cptr, "multikl") == 0) {
              if (lb->type < 0) {
                lb->type = MULTIKL;
              }
            }
            else if (strcmp(cptr, "spectral") == 0) {
              if (lb->type < 0) {
                lb->type = SPECTRAL;
              }
            }
            else if (strcmp(cptr, "scattered") == 0) {
              if (lb->type < 0) {
                lb->type = SCATTERED;
              }
            }
            else if (strcmp(cptr, "linear") == 0) {
              if (lb->type < 0) {
                lb->type = LINEAR;
              }
            }
            else if (strcmp(cptr, "inertial") == 0) {
              if (lb->type < 0) {
                lb->type = INERTIAL;
              }
            }
            else if (strcmp(cptr, "zpinch") == 0) {
              if (lb->type < 0) {
                lb->type = ZPINCH;
              }
            }
            else if (strcmp(cptr, "brick") == 0) {
              if (lb->type < 0) {
                lb->type = BRICK;
              }
            }
            else if (strcmp(cptr, "rcb") == 0) {
              if (lb->type < 0) {
                lb->type = ZOLTAN_RCB;
              }
            }
            else if (strcmp(cptr, "rib") == 0) {
              if (lb->type < 0) {
                lb->type = ZOLTAN_RIB;
              }
            }
            else if (strcmp(cptr, "hsfc") == 0) {
              if (lb->type < 0) {
                lb->type = ZOLTAN_HSFC;
              }
            }
            else if (strcmp(cptr, "random") == 0) {
              if (lb->type < 0) {
                lb->type = RANDOM;
              }
            }
            else if (strstr(cptr, "infile")) {
              if (lb->type < 0) {
                lb->type = INFILE;
                cptr2    = strchr(cptr, '=');
                if (cptr2 == nullptr) {
                  Gen_Error(0, "FATAL: need to specify a value with infile");
                  return 0;
                }

                cptr2++;
                iret     = sscanf(cptr2, "%2047s", tmpstr);
                lb->file = tmpstr;
                if (iret != 1) {
                  Gen_Error(0, "FATAL: invalid value for infile");
                  return 0;
                }
              }
            }
            else if (strcmp(cptr, "kl") == 0) {
              if (lb->refine < 0) {
                lb->refine = KL_REFINE;
              }
            }
            else if (strcmp(cptr, "none") == 0) {
              if (lb->refine < 0) {
                lb->refine = NS_NONE;
              }
            }
            else if (strcmp(cptr, "ignore_z") == 0) {
              lb->ignore_z = 1;
            }
            else if (strstr(cptr, "num_sects")) {
              if (lb->num_sects < 0) {
                cptr2 = strchr(cptr, '=');
                if (cptr2 == nullptr) {
                  Gen_Error(0, "FATAL: need to specify a value with num_sects");
                  return 0;
                }

                cptr2++;
                iret = sscanf(cptr2, "%d", &(lb->num_sects));
                if (iret != 1) {
                  Gen_Error(0, "FATAL: invalid value for num_sects");
                  return 0;
                }
              }
            }
            else if (strcmp(cptr, "cnctd_dom") == 0) {
              if (lb->cnctd_dom < 0) {
                lb->cnctd_dom = ELB_TRUE;
              }
            }
            else if (strstr(cptr, "outfile")) {
              if (lb->outfile < 0) {
                lb->outfile = ELB_TRUE;
                cptr2       = strchr(cptr, '=');
                if (cptr2 == nullptr) {
                  Gen_Error(0, "FATAL: need to specify a value with outfile");
                  return 0;
                }

                cptr2++;
                iret     = sscanf(cptr2, "%2047s", tmpstr);
                lb->file = tmpstr;
                if (iret != 1) {
                  Gen_Error(0, "FATAL: invalid value for outfile");
                  return 0;
                }
              }
            }
            else {
              ctemp =
                  fmt::format("FATAL: unknown LB method \"{}\" specified in command file", cptr);
              Gen_Error(0, ctemp);
              return 0;
            }
            cptr = strtok(nullptr, ",");
          }
        }
      }
      else if (token_compare(cptr, "solver specifications")) {
        /* Solver specs */

        /* Search to the first null character */
        cptr = strchr(cptr, '\0');
        cptr++;
        strip_string(cptr, " \t\n=");
        cptr = strtok(cptr, ",");

        /* Loop until all the suboptions have been specified */
        while (cptr) {
          strip_string(cptr, " \t\n");
          string_to_lower(cptr, '\0');

          /* Check to see if this is the "tolerance" suboption */
          if (strstr(cptr, "tolerance")) {
            if (solver->tolerance < 0.0) {
              cptr2 = strchr(cptr, '=');
              if (cptr2 == nullptr) {
                Gen_Error(0, "FATAL: tolerance specification requires a value");
                return 0;
              }

              cptr2++;
              iret = sscanf(cptr2, "%le", &(solver->tolerance));
              if (iret != 1) {
                Gen_Error(0, "FATAL: invalid value for tolerance");
                return 0;
              }
            }
          }
          else if (strcmp(cptr, "use_rqi") == 0) {
            if (solver->rqi_flag == USE_RQI) {
              solver->rqi_flag = -1;
            }
            else {
              solver->rqi_flag = USE_RQI;
            }
          }
          else if (strstr(cptr, "vmax")) {
            if (solver->vmax < 0) {
              cptr2 = strchr(cptr, '=');
              if (cptr2 == nullptr) {
                Gen_Error(0, "FATAL: vmax must have a value");
                return 0;
              }

              cptr2++;
              iret = sscanf(cptr2, "%d", &(solver->vmax));
              if (iret != 1) {
                Gen_Error(0, "FATAL: invalid value for vmax");
                return 0;
              }
            }
          }
          else {
            ctemp = fmt::format("WARNING: unknown solver suboption {}", cptr);
            Gen_Error(1, ctemp);
          }

          cptr = strtok(nullptr, ",");
        }
      }
      else if (token_compare(cptr, "graph type")) {
        if (problem->type < 0) {
          cptr = strtok(nullptr, "\t=");
          strip_string(cptr, " \t\n");
          string_to_lower(cptr, '\0');
          if (strcmp(cptr, "nodal") == 0) {
            problem->type = NODAL;
          }
          else if (strcmp(cptr, "node") == 0) {
            problem->type = NODAL;
          }
          else if (strcmp(cptr, "elemental") == 0) {
            problem->type = ELEMENTAL;
          }
          else if (strcmp(cptr, "element") == 0) {
            problem->type = ELEMENTAL;
          }
          else {
            Gen_Error(0, "FATAL: unknown graph type specified");
            return 0;
          }
        }
      }
      else if (token_compare(cptr, "machine description")) {
        /* Machine specs */
        if (machine->num_dims < 0) {
          /* Search to first null character */
          cptr = strchr(cptr, '\0');
          cptr++;
          strip_string(cptr, " \t\n=");

          /* Search to equal sign */
          cptr2 = strchr(cptr, '=');
          if (cptr2 == nullptr) {
            Gen_Error(0, "FATAL: machine must have a dimension specified");
            return 0;
          }

          *cptr2 = '\0';

          /* Find out the machine type */
          strip_string(cptr, " \t\n");
          if (strcasecmp(cptr, "mesh") == 0) {
            machine->type = MESH;
            max_dim       = 3;
          }
          else if (strcasecmp(cptr, "hcube") == 0 || strcasecmp(cptr, "hypercube") == 0) {
            machine->type = HCUBE;
            max_dim       = 1;
          }
          else if (strcasecmp(cptr, "cluster") == 0) {
            /* now need to find what each box consists of */
            cptr  = cptr2 + 1;
            cptr2 = strpbrk(cptr, "mMhH");
            if (*cptr2 == 'm' || *cptr2 == 'M') {
              machine->type = MESH;
              max_dim       = 3;
            }
            else if (*cptr2 == 'h' || *cptr2 == 'H') {
              machine->type = HCUBE;
              max_dim       = 1;
            }
            else {
              Gen_Error(0, "FATAL: unknown type specified with cluster");
              return 0;
            }
            /* blank out character and move cptr to next char */
            *cptr2 = '\0';

            /* get the number of boxes from value */
            iret = sscanf(cptr, "%d", &(machine->num_boxes));
            if (iret <= 0 || machine->num_boxes <= 0) {
              Gen_Error(0, "FATAL: invalid number of boxes");
              return 0;
            }
          }
          else {
            Gen_Error(0, "FATAL: unknown machine type specified");
            return 0;
          }

          machine->num_dims = 0;
          cptr              = cptr2 + 1;
          cptr              = strtok(cptr, "xX");
          while (cptr) {
            iret = sscanf(cptr, "%d", &(machine->dim[machine->num_dims]));
            if (iret != 1) {
              Gen_Error(0, "FATAL: invalid dimension specified for machine");
              return 0;
            }

            machine->num_dims++;
            cptr = strtok(nullptr, "xX");

            /* Check how many dimensions there are */
            if (machine->num_dims == max_dim && cptr != nullptr) {
              Gen_Error(0, "FATAL: maximum number of dimensions exceeded");
              return 0;
            }
          }
        }
      }
      else if (token_compare(cptr, "weighting specifications")) {
        /* Parameters for weighting the graph */
        if (weight->type < 0) {
          cptr = strchr(cptr, '\0');
          cptr++;
          strip_string(cptr, " \t\n=");
          cptr = strtok(cptr, ",");

          while (cptr != nullptr) {
            strip_string(cptr, " \t\n");
            string_to_lower(cptr, '\0');
            if (strstr(cptr, "read")) {
              cptr2 = strchr(cptr, '=');
              if (cptr2 == nullptr) {
                Gen_Error(0, "FATAL: must specify file name with \"read\"");
                return 0;
              }
              cptr2++;
              if (strlen(cptr2) == 0) {
                Gen_Error(0, "FATAL: invalid file name with \"read\"");
                return 0;
              }
              weight->exo_filename = cptr2;
              if (weight->type < 0) {
                weight->type = READ_EXO;
              }
              else if (!(weight->type & READ_EXO)) {
                weight->type += READ_EXO;
              }

              /* check if the read is after an element block weight */
              if (weight->type & EL_BLK) {
                weight->ow_read = 0;
              }
            }
            else if (strstr(cptr, "var_name")) {
              cptr2 = strchr(cptr, '=');
              if (cptr2 == nullptr) {
                Gen_Error(0, "FATAL: must specify a name with \"var_name\"");
                return 0;
              }
              cptr2++;
              if (strlen(cptr2) == 0) {
                Gen_Error(0, "FATAL: invalid variable name specified with"
                             " \"var_name\"");
                return 0;
              }
              weight->exo_varname = cptr2;
            }
            else if (strstr(cptr, "var_index")) {
              cptr2 = strchr(cptr, '=');
              if (cptr2 == nullptr) {
                Gen_Error(0, "FATAL: must specify a value with \"var_index\"");
                return 0;
              }
              cptr2++;

              iret = sscanf(cptr2, "%d", &(weight->exo_vindx));
              if (iret != 1) {
                Gen_Error(0, "FATAL: invalid value with \"var_index\"");
                return 0;
              }
            }
            else if (strstr(cptr, "time_index")) {
              cptr2 = strchr(cptr, '=');
              if (cptr2 == nullptr) {
                Gen_Error(0, "FATAL: must specify a value with \"time_index\"");
                return 0;
              }
              cptr2++;

              iret = sscanf(cptr2, "%d", &(weight->exo_tindx));
              if (iret != 1) {
                Gen_Error(0, "FATAL: invalid value with \"time_index\"");
                return 0;
              }
            }
            else if (strstr(cptr, "eb")) {
              cptr2 = strchr(cptr, '=');
              if (cptr2 == nullptr) {
                Gen_Error(0, "FATAL: must specify a value with \"eb\"");
                return 0;
              }
              cptr2++;

              el_blk = -1;
              wgt    = -1;

              iret = sscanf(cptr2, "%d:%d", &el_blk, &wgt);
              if (iret != 2) {
                Gen_Error(0, "FATAL: invalid value with \"eb\"");
                return 0;
              }
              if (el_blk <= 0) {
                ctemp = fmt::format("invalid element block, %d", el_blk);
                Gen_Error(0, ctemp);
                return 0;
              }
              if (wgt < 1) {
                ctemp = fmt::format("invalid weight, %d", wgt);
                Gen_Error(0, ctemp);
                return 0;
              }

              if (weight->type < 0) {
                weight->type = EL_BLK;
              }
              else if (!(weight->type & EL_BLK)) {
                weight->type += EL_BLK;
              }

              weight->elemblk.push_back(el_blk);
              weight->elemblk_wgt.push_back(wgt);

              /* check if the elem block weight needs to overwrite the read */
              if (weight->type & READ_EXO) {
                weight->ow_read = 1;
              }
            }
            else if (strstr(cptr, "edges")) {
              if (weight->type < 0) {
                weight->type = EDGE_WGT;
              }
              else if (!(weight->type & EDGE_WGT)) {
                weight->type += EDGE_WGT;
              }
            }
            else {
              ctemp = fmt::format("FATAL: unknown suboption \"{}\" specified", cptr);
              Gen_Error(0, ctemp);
              return 0;
            }

            cptr = strtok(nullptr, ",");
          }

        } /* End "if(weight->type < 0)" */
      }
      else if (token_compare(cptr, "misc options")) {
        /* Misc Options */

        /* Search to the first null character */
        cptr = strchr(cptr, '\0');
        cptr++;
        /*
         * Need to handle case where users have put comma's in
         * the group descriptor. This will mess up getting the
         * tokens using strtok(). So, search for commas between
         * the beginning delimiter, "{", and the end delimiter,
         * "}", and change them to blank spaces.
         */
        cptr2 = strchr(cptr, '{');
        if (cptr2 != nullptr) {
          ilen = strlen(cptr2);
          for (i = 0; i < ilen; i++) {
            if (*cptr2 == '}') {
              break;
            }
            if (*cptr2 == ',') {
              *cptr2 = ' ';
            }
            cptr2++;
          }
        }

        strip_string(cptr, " \t\n=");
        cptr = strtok(cptr, ",");

        /* Loop until all the suboptions have been specified */
        while (cptr != nullptr) {
          strip_string(cptr, " \t\n");
          string_to_lower(cptr, '\0');

          /* Check to see if the side id error checks need to be skipped */
          if (strstr(cptr, "checks_off")) {
            if (problem->skip_checks < 0) {
              problem->skip_checks = 1;
            }
          }
          /* Check to see if using face definition of adjacency */
          else if (strstr(cptr, "face_adj")) {
            if (problem->face_adj < 0) {
              problem->face_adj = 1;
            }
          }
          /* Check if element columns are to be detected and fixed so
           * that all elements of a column are in the same partition */
          else if (strstr(cptr, "fix_columns")) {
            problem->fix_columns = 1;
          }
          /* Check to see if looking for global mechanisms */
          else if (strstr(cptr, "global_mech")) {
            if (problem->global_mech < 0) {
              problem->global_mech = 1;
            }
          }
          /* Check to see if looking for introduced mechanisms */
          else if (strstr(cptr, "local_mech")) {
            if (problem->local_mech < 0) {
              problem->local_mech = 1;
            }
          }
          /* Check to see if looking for connected domains */
          else if (strstr(cptr, "find_cnt_domains")) {
            if (problem->find_cnt_domains < 0) {
              problem->find_cnt_domains = 1;
            }
          }
          /* Check to see if user wants to add processors to take care of
           * introduced mechanisms
           */
          else if (strstr(cptr, "mech_add_procs")) {
            if (problem->mech_add_procs < 0) {
              problem->mech_add_procs = 1;
            }
          }
          /* Check to see if user wants to add processors to take care of
           * introduced mechanisms
           */
          else if (strstr(cptr, "dsd_add_procs")) {
            if (problem->dsd_add_procs < 0) {
              problem->dsd_add_procs = 1;
            }
          }
          /* Check for treating spheres as concentrated masses*/
          else if (strstr(cptr, "no_sph")) {
            if (problem->no_sph < 0) {
              problem->no_sph = 1;
            }
          }
          /* Check for group designation sub-option */
          else if (strstr(cptr, "groups")) {
            /* "{" defines the beginning of the group designator */
            cptr2 = strchr(cptr, '{');
            if (cptr2 == nullptr) {
              Gen_Error(0, "FATAL: group start designator \"}\" not found");
              return 0;
            }
            cptr2++;
            /* allocate space to hold the group designator */
            problem->groups = reinterpret_cast<char *>(malloc(strlen(cptr2) + 1));
            copy_string(problem->groups, cptr2, strlen(cptr2) + 1);
            /* get rid of ending bracket */
            cptr2  = strchr(problem->groups, '}');
            *cptr2 = '\0';
          }
          else {
            ctemp = fmt::format("WARNING: unknown miscellaneous suboption {}", cptr);
            Gen_Error(1, ctemp);
          }
          cptr = strtok(nullptr, ",");
        }
      }
      else {
        /* Generate an error, but continue reading for an unknown key */
        strip_string(inp_copy, " #\t");
        if (strlen(inp_copy) > 5) {
          ctemp = fmt::format(
              "WARNING: don't know how to process line: \n{}\nin command file, ignored", inp_copy);
          Gen_Error(1, ctemp);
        }
      }

    } /* End "if(inp_line[0] != '#')" */

  } /* End "while(fgets(inp_line, MAX_INP_LINE, inp_fd))" */
  return 1;
} /*------------End read_cmd_file()---------------*/

/*****************************************************************************/
/*****************************************************************************/
/*****************************************************************************/
/* This function performs error checks on the user input.
 *---------------------------------------------------------------------------*/
template int check_inp_specs(std::string &exoII_inp_file, std::string &nemI_out_file,
                             Machine_Description *machine, LB_Description<int> *lb,
                             Problem_Description *prob, Solver_Description *solver,
                             Weight_Description<int> *weight);

template int check_inp_specs(std::string &exoII_inp_file, std::string &nemI_out_file,
                             Machine_Description *machine, LB_Description<int64_t> *lb,
                             Problem_Description *prob, Solver_Description *solver,
                             Weight_Description<int64_t> *weight);

template <typename INT>
int check_inp_specs(std::string &exoII_inp_file, std::string &nemI_out_file,
                    Machine_Description *machine, LB_Description<INT> *lb,
                    Problem_Description *prob, Solver_Description *solver,
                    Weight_Description<INT> *weight)
{
  /* Check that an input ExodusII file name was specified */
  if (exoII_inp_file.empty()) {
    Gen_Error(0, "FATAL: no input ExodusII file specified");
    return 0;
  }

  /* Check for the existence and readability of the input file */
  int   icpu_ws = 0;
  int   iio_ws  = 0;
  float vers;
  int   exid_inp;
  if ((exid_inp = ex_open(exoII_inp_file.c_str(), EX_READ, &icpu_ws, &iio_ws, &vers)) < 0) {
    std::string ctemp = fmt::format("FATAL: unable to open input ExodusII file {}", exoII_inp_file);
    Gen_Error(0, ctemp);
    return 0;
  }

  /* Get the integer size stored on the database */
  prob->int64db = ex_int64_status(exid_inp) & EX_ALL_INT64_DB;

  ex_close(exid_inp);
  /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  /*                       Check the machine specification                     */
  /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  if (machine->type != MESH && machine->type != HCUBE) {
    Gen_Error(0, "FATAL: machine type not properly set");
    return 0;
  }
  if (machine->type == HCUBE && machine->num_dims != 1) {
    Gen_Error(0, "FATAL: improper number of dimension for a hypercube, only"
                 " 1 allowed");
    return 0;
  }
  if (machine->type == MESH && machine->num_dims > 3) {
    Gen_Error(0, "FATAL: maximum of 3 dimensions for a mesh exceeded");
    return 0;
  }

  /* non-cluster machines have only one box */
  if (machine->num_boxes < 0) {
    machine->num_boxes = 1;
  }

  /* Find out the number of processors */
  if (machine->type == HCUBE) {
    machine->procs_per_box = 1 << machine->dim[0];
  }
  else {
    machine->procs_per_box = machine->dim[0];
    for (int cnt = 1; cnt < machine->num_dims; cnt++) {
      machine->procs_per_box *= machine->dim[cnt];
    }
  }

  /* now calculate the total number of processors */
  machine->num_procs = machine->num_boxes * machine->procs_per_box;

  /*
   * currently, do not allow groups and clusters since the
   * loops to chaco get a bit too confusing
   */
  if (machine->num_boxes > 1 && prob->groups != nullptr) {
    Gen_Error(0, "FATAL: groups cannot be designated for a cluster machine");
    return 0;
  }

  /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  /*                      Check the problem specifications                     */
  /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  if (prob->type != ELEMENTAL && prob->type != NODAL) {
    Gen_Error(0, "FATAL: unknown problem type specified");
    return 0;
  }

  if (prob->skip_checks < 0) {
    prob->skip_checks = 0;
  }

  if (prob->face_adj < 0) {
    prob->face_adj = 0;
  }

  /*
   * using face definition of adjacencies only makes sense
   * with an elemental decomposition
   */
  if (prob->type != ELEMENTAL && prob->face_adj) {
    Gen_Error(1, "WARNING: can only use face definition of");
    Gen_Error(1, "WARNING: adjacency with elemental decomposition");
    Gen_Error(1, "WARNING: face definition turned off");
    prob->face_adj = 0;
  }

  /*
   * Detecting columns and fixing their partitioning only makes sense
   * with an elemental decomposition
   */
  if (prob->type != ELEMENTAL && prob->fix_columns) {
    Gen_Error(1, "WARNING: can only use fix columns options");
    Gen_Error(1, "WARNING: with elemental decomposition");
    Gen_Error(1, "WARNING: fix columns option turned off");
    prob->fix_columns = 0;
  }

  /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  /*                      Check the load balance parameters                    */
  /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  if (lb->type != MULTIKL && lb->type != SPECTRAL && lb->type != INERTIAL && lb->type != LINEAR &&
      lb->type != RANDOM && lb->type != SCATTERED && lb->type != INFILE && lb->type != ZPINCH &&
      lb->type != BRICK && lb->type != ZOLTAN_RCB && lb->type != ZOLTAN_RIB &&
      lb->type != ZOLTAN_HSFC) {
    Gen_Error(0, "FATAL: unknown load balance type requested");
    return 0;
  }

  if ((sizeof(INT) == 8) && (lb->type != LINEAR && lb->type != SCATTERED)) {
    Gen_Error(1, "WARNING: This mesh is using 64-bit integers. The only supported");
    Gen_Error(1, "         load balance methods for 64-bit integers are 'linear' or 'scattered'");
    Gen_Error(1, "         The load balance method will be automatically changed to 'linear'.");
    lb->type = LINEAR;
  }

  if (lb->type == MULTIKL) {
    lb->refine = KL_REFINE;
  }

  if (lb->refine != KL_REFINE && lb->refine != NO_REFINE) {
    lb->refine = NO_REFINE; /* Default if not specified */
  }

  if (lb->num_sects <= 0) {
    lb->num_sects = 1; /* Default if not specified */
  }

  if (lb->cnctd_dom < 0) {
    lb->cnctd_dom = 0;
  }
  else if (!prob->face_adj) {
    Gen_Error(1, "WARNING: can only set connected domain");
    Gen_Error(1, "WARNING: when using face definition of adjacency");
    Gen_Error(1, "WARNING: connected domain turned off");
    lb->cnctd_dom = 0;
  }

  if (lb->outfile < 0) {
    lb->outfile = ELB_FALSE;
  }

  if (lb->type == INFILE) {
    if (lb->outfile) {
      Gen_Error(0, "FATAL: both infile and outfile cannot be specified");
      return 0;
    }

    if (lb->refine != NO_REFINE) {
      Gen_Error(1, "WARNING: no refinement can be specified with infile");
      return 0;
    }
  }

  /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  /*                   Check the eigensolver parameters                        */
  /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  if (lb->type == SPECTRAL || lb->type == MULTIKL) {
    if (solver->tolerance < 0.0) {
      Gen_Error(1, "WARNING: using default value for eigensolver"
                   " tolerance");
      solver->tolerance = 1.0e-3;
    }

    if (solver->rqi_flag < 0) {
      solver->rqi_flag = 0;
    }

    if (solver->vmax < 0) {
      Gen_Error(1, "WARNING: no value for vmax specified,"
                   " using default");
      solver->vmax = 200;
    }
  }

  /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  /*                     Check the output file name                            */
  /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  if (nemI_out_file.empty()) {
    /*
     * Generate the file name from the input file name and the requested
     * load balance method.
     */
    std::string ctemp2;
    switch (machine->type) {
    case MESH: ctemp2 = fmt::format("-m{}-", machine->num_procs); break;

    case HCUBE: ctemp2 = fmt::format("-h{}-", machine->num_procs); break;
    }

    switch (lb->type) {
    case MULTIKL:
    case SPECTRAL:
      if (lb->num_sects == 1) {
        ctemp2 += "b";
      }
      else if (lb->num_sects == 2) {
        ctemp2 += "q";
      }
      else if (lb->num_sects == 3) {
        ctemp2 += "o";
      }

      break;

    case INERTIAL: ctemp2 += "i"; break;

    case ZPINCH: ctemp2 += "z"; break;

    case BRICK: ctemp2 += "x"; break;

    case ZOLTAN_RCB:
    case ZOLTAN_RIB:
    case ZOLTAN_HSFC: ctemp2 += "Z"; break;

    case SCATTERED: ctemp2 += "s"; break;

    case RANDOM: ctemp2 += "r"; break;

    case LINEAR: ctemp2 += "l"; break;
    }

    if (lb->refine == KL_REFINE) {
      ctemp2 += "KL";
    }

    /* Generate the complete file name */

    nemI_out_file = remove_extension(exoII_inp_file);
    nemI_out_file += ctemp2;
    nemI_out_file += ".nemI";
  } /* End "if(strlen(nemI_out_file) <= 0)" */

  /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  /*                 Check the weighting specifications                        */
  /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  if (weight->exo_filename.length() > 0) {
    /* Check that a variable name and/or index was specified. */
    if (weight->exo_varname.empty() && weight->exo_vindx <= 0) {
      Gen_Error(0, "FATAL: must specify an index and/or a name for weighting"
                   " variable");
      return 0;
    }

    /*
     * If a variable name and or index was specified then open the ExodusII
     * file and compare the specified name against what exists in the file.
     */
    int   exoid;
    float version;
    int   cpu_ws = 0;
    int   io_ws  = 0;
    if ((exoid = ex_open(weight->exo_filename.c_str(), EX_READ, &cpu_ws, &io_ws, &version)) < 0) {
      std::string ctemp =
          fmt::format("FATAL: failed to open ExodusII weighting file {}", weight->exo_filename);
      Gen_Error(0, ctemp);
      return 0;
    }

    int ntimes = ex_inquire_int(exoid, EX_INQ_TIME);

    /* Check the time index */
    if (weight->exo_tindx <= 0) {
      weight->exo_tindx = 1; /* Defaults to 1 */
    }

    if (weight->exo_tindx > ntimes) {
      std::string ctemp = fmt::format(
          "FATAL: requested time index %d not available in weighting file", weight->exo_tindx);
      Gen_Error(0, ctemp);
      return 0;
    }

    ex_entity_type type;
    if (prob->type == NODAL) {
      type = EX_NODAL;
    }
    else {
      type = EX_ELEM_BLOCK;
    }

    /*
     * First check that there are variables of the requested type in the
     * specified ExodusII file.
     */
    int nvars;
    if (ex_get_variable_param(exoid, type, &nvars) < 0) {
      Gen_Error(0, "FATAL: unable to get variable params from ExodusII"
                   " weighting file");
      return 0;
    }
    if (nvars <= 0) {
      Gen_Error(0, "FATAL: no variables found in the ExodusII weighting file");
      return 0;
    }

    /* Read the variable names from the requested file */
    char **var_names = reinterpret_cast<char **>(malloc(nvars * sizeof(char *)));
    if (!var_names) {
      Gen_Error(0, "FATAL: insufficient memory");
      return 0;
    }

    {
      int max_name_length = ex_inquire_int(exoid, EX_INQ_DB_MAX_USED_NAME_LENGTH);
      ex_set_max_name_length(exoid, max_name_length);

      for (int cnt = 0; cnt < nvars; cnt++) {
        var_names[cnt] = reinterpret_cast<char *>(malloc((max_name_length + 1) * sizeof(char)));
        if (!var_names[cnt]) {
          Gen_Error(0, "FATAL: insufficient memory");
          return 0;
        }
      }
    }

    if (ex_get_variable_names(exoid, type, nvars, var_names) < 0) {
      Gen_Error(0, "FATAL: unable to obtain variable names for weighting");
      return 0;
    }

    /*
     * If there was a variable name specified but no index then get the
     * index. If a variable name AND an index were specified then make
     * sure they match.
     */
    if (!weight->exo_varname.empty()) {
      int tmp_vindx = 0;
      for (int cnt = 0; cnt < nvars; cnt++) {
        if (strcmp(var_names[cnt], weight->exo_varname.c_str()) == 0) {
          tmp_vindx = cnt + 1;

          break; /* out of "for" loop */
        }
      }

      if (weight->exo_vindx <= 0) {
        weight->exo_vindx = tmp_vindx;
      }
      else if (weight->exo_vindx != tmp_vindx) {
        Gen_Error(0, "FATAL: requested weight variable index doesn't match"
                     " the variable name in the ExodusII file");
        return 0;
      }
    }

    /* Free up memory */
    for (int cnt = 0; cnt < nvars; cnt++) {
      free(var_names[cnt]);
    }
    free(var_names);

    /*
     * If there is still no valid index then the variable name does
     * not exist in the specified file.
     */
    if (weight->exo_vindx <= 0) {
      std::string ctemp = fmt::format(
          "FATAL: requested weighting variable {} not found in ExodusII file", weight->exo_varname);
      Gen_Error(0, ctemp);
      return 0;
    }

    if (nvars < weight->exo_vindx) {
      Gen_Error(0, "FATAL: requested variable index is larger than number in"
                   " ExodusII weighting file");
      return 0;
    }

    ex_close(exoid);

  } /* End "if(strlen(weight->exo_filename) > 0)" */

  if ((weight->type & EL_BLK) && (weight->ow_read)) {
    if (weight->elemblk.size() > 1) {
      /* start by sorting the two arrays by the element block number */
      sort2(weight->elemblk.size(), weight->elemblk.data(), weight->elemblk_wgt.data());

      /* now loop through, and make sure that we don't have multiple values */
      for (int cnt = 1; cnt < (int)weight->elemblk.size(); cnt++) {
        if (weight->elemblk[cnt] == weight->elemblk[cnt - 1]) {
          std::string ctemp =
              fmt::format("WARNING: multiple weight specified for block {}", weight->elemblk[cnt]);
          Gen_Error(1, ctemp);
        }
      }
    }
  } /* if ((weight->type & EL_BLK) && (weight->ow_read)) */

  return 1;

} /*-------------------End check_inp_specs()-----------------*/

namespace {
  void print_usage()
  {
    fmt::print("\nusage:\t{} [-h] [<-n|-e> -o <output file>"
               " -m <machine description>\n"
               "\t -l <load bal description> -s <eigen solver specs>\n"
               "\t -w <weighting options> -g <group list> -f]\n"
               "\t [-a <ascii file>] exoII_file\n\n"
               " -32\t\tforce use of 32-bit integers\n"
               " -64\t\tforce use of 64-bit integers\n"
               " -n\t\tperform a nodal based load balance\n"
               " -e\t\tperform an elemental based load balance\n"
               " -o NemI file\toutput NemesisI load-balance file name\n"
               " -m mach desc\tdescription of the machine to load balance for\n"
               " -l LB data\tload balance specifications\n"
               " -s Eigen specs\tEigen solver specifications\n"
               " -w weighting\tweighting specs for load balance\n"
               " -g groupings\tgrouping specifications for load balance\n"
               " -f\t\tuse face definition of adjacency\n"
               " -p\t\tuse partial definition of adjacency: \n"
               "   \t\trequire only 3 matching quad face nodes\n"
               " -C\tavoid splitting vertical element columns\n"
               "   \t\tacross partitions\n"
               " -h\t\tusage information\n"
               " -a ascii file\tget info from ascii input file name\n",
               UTIL_NAME);
  }
} // namespace
