// 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

#include "CJ_CodeTypes.h" // for StringIdVector, etc
#include "CJ_ExodusFile.h"
#include "CJ_SystemInterface.h"
#include "open_file_limit.h"
#include "smart_assert.h"

#include <climits>
#include <cstdlib>
#include <fmt/ostream.h>
#include <iomanip>
#include <iostream>
#include <string>

#include <exodusII.h>

std::vector<int>         Excn::ExodusFile::fileids_;
std::vector<std::string> Excn::ExodusFile::filenames_;
int                      Excn::ExodusFile::outputId_    = -1;
int                      Excn::ExodusFile::ioWordSize_  = 0;
int                      Excn::ExodusFile::cpuWordSize_ = 0;
std::string              Excn::ExodusFile::outputFilename_;
bool                     Excn::ExodusFile::keepOpen_          = false;
int                      Excn::ExodusFile::maximumNameLength_ = 32;
int                      Excn::ExodusFile::exodusMode_        = 0;

Excn::ExodusFile::ExodusFile(size_t which) : myLocation_(which)
{
  SMART_ASSERT(which < filenames_.size())(which)(filenames_.size());
  SMART_ASSERT(fileids_.size() == filenames_.size());
  if (!keepOpen_ && which != 0) {
    float version       = 0.0;
    int   cpu_word_size = cpuWordSize_;
    int   io_wrd_size   = ioWordSize_;
    SMART_ASSERT(fileids_[which] == -1)(which)(fileids_[which]);
    fileids_[which] = ex_open(filenames_[which].c_str(), EX_READ | exodusMode_, &cpu_word_size,
                              &io_wrd_size, &version);
    if (fileids_[which] < 0) {
      fmt::print(stderr, "ERROR: Cannot open file '{}' - exiting\n", filenames_[which]);
      exit(1);
    }
    ex_set_max_name_length(fileids_[which], maximumNameLength_);

    SMART_ASSERT(io_wrd_size == ioWordSize_);
    SMART_ASSERT(cpu_word_size == cpuWordSize_);
  }
}

int Excn::ExodusFile::output()
{
  SMART_ASSERT(outputId_ >= 0);
  return outputId_;
}

Excn::ExodusFile::operator int() const
{
  SMART_ASSERT(fileids_[myLocation_] >= 0);
  return fileids_[myLocation_];
}

Excn::ExodusFile::~ExodusFile()
{
  try {
    if (!keepOpen_ && myLocation_ != 0) {
      SMART_ASSERT(fileids_[myLocation_] > 0)(myLocation_)(fileids_[myLocation_]);
      ex_close(fileids_[myLocation_]);
      fileids_[myLocation_] = -1;
    }
  }
  catch (...) {
  }
}

void Excn::ExodusFile::close_all()
{
  for (auto &elem : fileids_) {
    if (elem > 0) {
      ex_close(elem);
    }
    elem = -1;
  }
  ex_close(outputId_);
  outputId_ = -1;
}

bool Excn::ExodusFile::initialize(const SystemInterface &si)
{
  // See if we can keep files open
  size_t max_files = open_file_limit() - 1; // We also have an output exodus file...
  if (si.inputFiles_.size() <= max_files) {
    keepOpen_ = true;
    if ((si.debug() & 1) != 0) {
      fmt::print("Files kept open... (Max open = {})\n\n", max_files);
    }
  }
  else {
    keepOpen_ = false;
    fmt::print("Single file mode... (Max open = {})\n\n", max_files);
  }

  float version = 0.0;

  // create exo names
  filenames_.resize(si.inputFiles_.size());
  fileids_.resize(si.inputFiles_.size(), -1);

  int overall_max_name_length = 32;
  for (size_t p = 0; p < si.inputFiles_.size(); p++) {
    std::string name = si.inputFiles_[p];

    filenames_[p] = name;

    if (p == 0) {
      int cpu_word_size = sizeof(float);
      int io_wrd_size   = 0;
      int exoid = ex_open(filenames_[p].c_str(), EX_READ, &cpu_word_size, &io_wrd_size, &version);
      if (exoid < 0) {
        fmt::print(stderr, "ERROR: Cannot open file '{}'\n", filenames_[p]);
        return false;
      }

      int max_name_length = ex_inquire_int(exoid, EX_INQ_DB_MAX_USED_NAME_LENGTH);
      if (max_name_length > overall_max_name_length) {
        overall_max_name_length = max_name_length;
      }

      ex_close(exoid);

      if (io_wrd_size < static_cast<int>(sizeof(float))) {
        io_wrd_size = sizeof(float);
      }

      ioWordSize_  = io_wrd_size;
      cpuWordSize_ = io_wrd_size;

      if ((ex_int64_status(exoid & EX_ALL_INT64_DB) != 0) || si.ints_64_bit()) {
        exodusMode_ = EX_ALL_INT64_API;
      }
    }

    if (keepOpen_ || p == 0) {
      int io_wrd_size = 0;
      int mode        = EX_READ | exodusMode_;

      fileids_[p] = ex_open(filenames_[p].c_str(), mode, &cpuWordSize_, &io_wrd_size, &version);
      if (fileids_[p] < 0) {
        fmt::print(stderr, "ERROR: Cannot open file '{}'\n", filenames_[p]);
        return false;
      }

      SMART_ASSERT(ioWordSize_ == io_wrd_size)(ioWordSize_)(io_wrd_size);
    }

    fmt::print("Part {}: '{}'\n", p + 1, name);
  }

  maximumNameLength_ = overall_max_name_length;
  if (keepOpen_) {
    for (size_t p = 0; p < si.inputFiles_.size(); p++) {
      ex_set_max_name_length(fileids_[p], maximumNameLength_);
    }
  }
  else {
    ex_set_max_name_length(fileids_[0], maximumNameLength_);
  }
  return true;
}

bool Excn::ExodusFile::create_output(const SystemInterface &si)
// Create output file...
{
  outputFilename_ = si.outputName_;

  int mode = EX_CLOBBER | exodusMode_;
  if (si.ints_64_bit()) {
    mode |= EX_ALL_INT64_DB;
  }

  if (si.compress_data() > 0 || si.use_netcdf4() || si.szip()) {
    mode |= EX_NETCDF4;
  }

  fmt::print("Output:   '{}'\n", outputFilename_);
  outputId_ = ex_create(outputFilename_.c_str(), mode, &cpuWordSize_, &ioWordSize_);
  if (outputId_ < 0) {
    fmt::print(stderr, "ERROR: Cannot open file '{}'\n", outputFilename_);
    return false;
  }

  if (si.compress_data() > 0) {
    ex_set_option(outputId_, EX_OPT_COMPRESSION_LEVEL, si.compress_data());
    ex_set_option(outputId_, EX_OPT_COMPRESSION_SHUFFLE, 1);

    if (si.szip()) {
      ex_set_option(outputId_, EX_OPT_COMPRESSION_TYPE, EX_COMPRESS_SZIP);
    }
    else if (si.zlib()) {
      ex_set_option(outputId_, EX_OPT_COMPRESSION_TYPE, EX_COMPRESS_ZLIB);
    }
  }

  fmt::print("IO Word size is {} bytes.\n", ioWordSize_);
  ex_set_max_name_length(outputId_, maximumNameLength_);
  return true;
}
