/*
 * ADIOS1Writer.cpp
 * Class to write files using old adios 1.x library.
 * It requires adios 1.x installed
 *
 *  Created on: Mar 27, 2017
 *      Author: pnb
 */

#include "engine/adios1/ADIOS1Writer.h"
#include "ADIOS.h"

extern int adios_verbose_level;
extern int adios_errno;

namespace adios
{

ADIOS1Writer::ADIOS1Writer(ADIOS &adios, const std::string name,
                           const std::string accessMode, MPI_Comm mpiComm,
                           const Method &method, const IOMode iomode,
                           const float timeout_sec, const bool debugMode,
                           const unsigned int nthreads)
    : Engine(adios, "ADIOS1Writer", name, accessMode, mpiComm, method,
             debugMode, nthreads,
             " ADIOS1Writer constructor (or call to ADIOS Open).\n"),
      m_groupname{method.m_Name.c_str()}, m_filename{name.c_str()},
      m_comm{mpiComm}
{
  Init();
  adios_open(&m_adios_file, m_groupname, m_filename, accessMode.c_str(),
             m_comm);
  if (adios_errno == err_no_error)
  {
    m_IsFileOpen = true;
  }
}

ADIOS1Writer::~ADIOS1Writer()
{
  if (m_IsFileOpen)
  {
    adios_close(m_adios_file);
    m_IsFileOpen = false;
  }
}

void ADIOS1Writer::Init()
{
  if (!m_initialized)
  {
    adios_init_noxml(m_comm);
    m_initialized = true;
  }
  adios_declare_group(&m_adios_group, m_groupname, "", adios_stat_default);

  InitParameters();
  InitTransports();
}

bool ADIOS1Writer::ReOpenAsNeeded()
{
  if (!m_IsFileOpen)
  {
    adios_open(&m_adios_file, m_groupname, m_filename, "a", m_comm);
    if (adios_errno == err_no_error)
    {
      m_IsFileOpen = true;
      adios_delete_vardefs(m_adios_group);
    }
  }
  return m_IsFileOpen;
}

void ADIOS1Writer::DefineVariable(std::string name, bool isScalar,
                                  enum ADIOS_DATATYPES vartype,
                                  std::string ldims, std::string gdims,
                                  std::string offs)
{
  if (isScalar)
  {
    adios_define_var(m_adios_group, name.c_str(), "", vartype, "", "", "");
  }
  else
  {
    adios_define_var(m_adios_group, name.c_str(), "", vartype, ldims.c_str(),
                     gdims.c_str(), offs.c_str());
  }
}

void ADIOS1Writer::WriteVariable(std::string name, bool isScalar,
                                 enum ADIOS_DATATYPES vartype,
                                 std::string ldims, std::string gdims,
                                 std::string offs, const void *values)
{
  if (ReOpenAsNeeded())
  {
    DefineVariable(name, isScalar, vartype, ldims, gdims, offs);
    adios_write(m_adios_file, name.c_str(), values);
  }
}

void ADIOS1Writer::Write(Variable<char> &variable, const char *values)
{
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_byte,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<unsigned char> &variable,
                         const unsigned char *values)
{
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_unsigned_byte,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<short> &variable, const short *values)
{
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_short,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<unsigned short> &variable,
                         const unsigned short *values)
{
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_unsigned_short,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<int> &variable, const int *values)
{
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_integer,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<unsigned int> &variable,
                         const unsigned int *values)
{
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_unsigned_integer,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<long int> &variable, const long int *values)
{
  enum ADIOS_DATATYPES type =
      adios_integer; // long int is usually 4 bytes which is adios_integer
  if (sizeof(long int) == 8)
  {
    type = adios_long;
  }
  WriteVariable(variable.m_Name, variable.m_IsScalar, type,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<unsigned long int> &variable,
                         const unsigned long int *values)
{
  enum ADIOS_DATATYPES type =
      adios_unsigned_integer; // long int is usually 4 bytes
  if (sizeof(long int) == 8)
  {
    type = adios_unsigned_long;
  }
  WriteVariable(variable.m_Name, variable.m_IsScalar, type,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<long long int> &variable,
                         const long long int *values)
{
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_long,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<unsigned long long int> &variable,
                         const unsigned long long int *values)
{
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_unsigned_long,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<float> &variable, const float *values)
{
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_real,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<double> &variable, const double *values)
{
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_double,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<long double> &variable,
                         const long double *values)
{
  /* TODO: This is faulty: adios_long_double expects 16 bytes per elements, but
   * long double is compiler dependent */
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_long_double,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<std::complex<float>> &variable,
                         const std::complex<float> *values)
{
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_complex,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<std::complex<double>> &variable,
                         const std::complex<double> *values)
{
  WriteVariable(variable.m_Name, variable.m_IsScalar, adios_double_complex,
                variable.GetDimensionAsString(),
                variable.GetGlobalDimensionAsString(),
                variable.GetOffsetsAsString(), values);
}

void ADIOS1Writer::Write(Variable<std::complex<long double>> &variable,
                         const std::complex<long double> *values)
{
  throw std::invalid_argument("ERROR: Adios 1.x does not support complex<long "
                              "double> type, so it cannot write variable " +
                              variable.m_Name + "\n");
}

void ADIOS1Writer::Write(VariableCompound &variable, const void *values)
{
  throw std::invalid_argument("ERROR: Adios 1.x does not support compound "
                              "types, so it cannot write variable " +
                              variable.m_Name + "\n");
}

// String version
void ADIOS1Writer::Write(const std::string variableName, const char *values)
{
  Write(m_ADIOS.GetVariable<char>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName,
                         const unsigned char *values)
{
  Write(m_ADIOS.GetVariable<unsigned char>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName, const short *values)
{
  Write(m_ADIOS.GetVariable<short>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName,
                         const unsigned short *values)
{
  Write(m_ADIOS.GetVariable<unsigned short>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName, const int *values)
{
  Write(m_ADIOS.GetVariable<int>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName,
                         const unsigned int *values)
{
  Write(m_ADIOS.GetVariable<unsigned int>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName, const long int *values)
{
  Write(m_ADIOS.GetVariable<long int>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName,
                         const unsigned long int *values)
{
  Write(m_ADIOS.GetVariable<unsigned long int>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName,
                         const long long int *values)
{
  Write(m_ADIOS.GetVariable<long long int>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName,
                         const unsigned long long int *values)
{
  Write(m_ADIOS.GetVariable<unsigned long long int>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName, const float *values)
{
  Write(m_ADIOS.GetVariable<float>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName, const double *values)
{
  Write(m_ADIOS.GetVariable<double>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName,
                         const long double *values)
{
  Write(m_ADIOS.GetVariable<long double>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName,
                         const std::complex<float> *values)
{
  Write(m_ADIOS.GetVariable<std::complex<float>>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName,
                         const std::complex<double> *values)
{
  Write(m_ADIOS.GetVariable<std::complex<double>>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName,
                         const std::complex<long double> *values)
{
  Write(m_ADIOS.GetVariable<std::complex<long double>>(variableName), values);
}

void ADIOS1Writer::Write(const std::string variableName,
                         const void *values) // Compound type
{
  throw std::invalid_argument("ERROR: Adios 1.x does not support compound "
                              "types, so it cannot write variable " +
                              variableName + "\n");
}

void ADIOS1Writer::Advance()
{
  if (m_IsFileOpen)
  {
    adios_close(m_adios_file);
    m_IsFileOpen = false;
  }
}

void ADIOS1Writer::Close(const int transportIndex)
{
  if (m_IsFileOpen)
  {
    adios_close(m_adios_file);
    m_IsFileOpen = false;
  }
}

// PRIVATE FUNCTIONS
void ADIOS1Writer::InitParameters()
{
  auto itMaxBufferSize = m_Method.m_Parameters.find("max_size_MB");
  if (itMaxBufferSize != m_Method.m_Parameters.end())
  {
    adios_set_max_buffer_size(std::stoul(itMaxBufferSize->second));
  }

  auto itVerbosity = m_Method.m_Parameters.find("verbose");
  if (itVerbosity != m_Method.m_Parameters.end())
  {
    int verbosity = std::stoi(itVerbosity->second);
    if (m_DebugMode == true)
    {
      if (verbosity < 0 || verbosity > 5)
        throw std::invalid_argument("ERROR: Method verbose argument must be an "
                                    "integer in the range [0,5], in call to "
                                    "Open or Engine constructor\n");
    }
    adios_verbose_level = verbosity;
  }
}

void ADIOS1Writer::InitTransports()
{
  if (m_DebugMode == true)
  {
    if (TransportNamesUniqueness() == false)
    {
      throw std::invalid_argument(
          "ERROR: two transports of the same kind (e.g file IO) "
          "can't have the same name, modify with name= in Method "
          "AddTransport\n");
    }
  }

  for (const auto &parameters : m_Method.m_TransportParameters)
  {
    auto itTransport = parameters.find("transport");

    if (itTransport->second == "file" || itTransport->second == "File")
    {
      auto itLibrary = parameters.find("library");
      if (itLibrary == parameters.end() ||
          itLibrary->second == "POSIX") // use default POSIX
      {
        adios_select_method(m_adios_group, "POSIX", "", "");
      }
      else if (itLibrary->second == "MPI_File" || itLibrary->second == "MPI-IO")
      {
        adios_select_method(m_adios_group, "MPI", "", "");
      }
      else
      {
        if (m_DebugMode == true)
          throw std::invalid_argument(
              "ERROR: file transport library " + itLibrary->second +
              " not supported, in " + m_Name + m_EndMessage);
      }
    }
    else
    {
      if (m_DebugMode == true)
        throw std::invalid_argument("ERROR: transport " + itTransport->second +
                                    " (you mean File?) not supported, in " +
                                    m_Name + m_EndMessage);
    }
  }
}

} // end namespace adios