/*
 * NCWriteHelper.cpp
 *
 *  Created on: Mar 28, 2014
 *      Author: iulian
 */

#include "NCWriteHelper.hpp"
#include "NCWriteEuler.hpp"
#include "NCWriteFV.hpp"
#include "NCWriteHOMME.hpp"
#include "NCWriteMPAS.hpp"
#include "NCWriteGCRM.hpp"

#include "moab/WriteUtilIface.hpp"
#include "MBTagConventions.hpp"

#include <sstream>

#ifdef _WIN32
#ifdef size_t
#undef size_t
#endif
#endif

namespace moab {

//! Get appropriate helper instance for WriteNC class; based on some info in the file set
NCWriteHelper* NCWriteHelper::get_nc_helper(WriteNC* writeNC, int fileId, const FileOptions& opts, EntityHandle fileSet)
{
  std::string& grid_type = writeNC->grid_type;
  if (grid_type == "CAM_EUL")
    return new (std::nothrow) NCWriteEuler(writeNC, fileId, opts, fileSet);
  else if (grid_type == "CAM_FV")
    return new (std::nothrow) NCWriteFV(writeNC, fileId, opts, fileSet);
  else if (grid_type == "CAM_SE")
    return new (std::nothrow) NCWriteHOMME(writeNC, fileId, opts, fileSet);
  else if (grid_type == "MPAS")
    return new (std::nothrow) NCWriteMPAS(writeNC, fileId, opts, fileSet);
  else if (grid_type == "GCRM")
    return new (std::nothrow) NCWriteGCRM(writeNC, fileId, opts, fileSet);

  // Unknown NetCDF grid
  return NULL;
}

ErrorCode NCWriteHelper::collect_variable_data(std::vector<std::string>& var_names, std::vector<int>& tstep_nums)
{
  Interface*& mbImpl = _writeNC->mbImpl;
  std::vector<std::string>& dimNames = _writeNC->dimNames;
  std::vector<int>& dimLens = _writeNC->dimLens;
  std::set<std::string>& usedCoordinates = _writeNC->usedCoordinates;
  std::set<std::string>& dummyVarNames = _writeNC->dummyVarNames;
  std::map<std::string, WriteNC::VarData>& varInfo = _writeNC->varInfo;
  DebugOutput& dbgOut = _writeNC->dbgOut;

  ErrorCode rval;

  usedCoordinates.clear();

  if (tstep_nums.empty() && nTimeSteps > 0) {
    // No timesteps input, get them all
    for (int i = 0; i < nTimeSteps; i++)
      tstep_nums.push_back(i);
  }

  for (size_t i = 0; i < var_names.size(); i++) {
    std::string varname = var_names[i];
    std::map<std::string, WriteNC::VarData>::iterator vit = varInfo.find(varname);
    if (vit == varInfo.end())
      MB_SET_ERR(MB_FAILURE, "Can't find variable " << varname);

    WriteNC::VarData& currentVarData = vit->second;

    dbgOut.tprintf(2, "    for variable %s varDims.size %d \n", varname.c_str(), (int)currentVarData.varDims.size());
    for (size_t j = 0; j < currentVarData.varDims.size(); j++) {
      std::string dimName = dimNames[currentVarData.varDims[j]];
      vit = varInfo.find(dimName);
      if (vit == varInfo.end())
        MB_SET_ERR(MB_FAILURE, "Can't find coordinate variable " << dimName);

      usedCoordinates.insert(dimName); // Collect those used, we will need to write them to the file
      dbgOut.tprintf(2, "    for variable %s need dimension %s with length %d\n", varname.c_str(), dimName.c_str(), dimLens[currentVarData.varDims[j]]);
    }

    // Process coordinate variables later
    if (usedCoordinates.find(varname) != usedCoordinates.end())
      continue;

    // Default has_tsteps is false
    if (std::find(currentVarData.varDims.begin(), currentVarData.varDims.end(), tDim) != currentVarData.varDims.end())
      currentVarData.has_tsteps = true;

    // Default numLev is 0
    if ((std::find(currentVarData.varDims.begin(), currentVarData.varDims.end(), levDim) != currentVarData.varDims.end()))
      currentVarData.numLev = nLevels;

    // Process set variables
    if (WriteNC::ENTLOCSET == currentVarData.entLoc) {
      if (currentVarData.has_tsteps) {
        // Set variables with timesteps, e.g. xtime(Time) or xtime(Time, StrLen)
        // TBD
        MB_SET_ERR(MB_NOT_IMPLEMENTED, "Writing set variables with timesteps is not implemented yet");
      }
      else {
        // Get the tag with varname
        Tag tag = 0;
        rval = mbImpl->tag_get_handle(varname.c_str(), tag);MB_CHK_SET_ERR(rval, "Can't find tag " << varname);
        currentVarData.varTags.push_back(tag); // Really, only one for these
        const void* data;
        int size;
        rval = mbImpl->tag_get_by_ptr(tag, &_fileSet, 1, &data, &size);MB_CHK_SET_ERR(rval, "Can't get data of tag " << varname);

        // Find the type of tag, and use it
        DataType type;
        rval = mbImpl->tag_get_data_type(tag, type);MB_CHK_SET_ERR(rval, "Can't get data type of tag " << varname);

        currentVarData.varDataType = NC_DOUBLE;
        if (MB_TYPE_INTEGER == type)
          currentVarData.varDataType = NC_INT;

        assert(0 == currentVarData.memoryHogs.size()); // Nothing so far
        currentVarData.memoryHogs.push_back((void*)data);

        if (currentVarData.varDims.empty()) {
          // Scalar variable
          currentVarData.writeStarts.push_back(0);
          currentVarData.writeCounts.push_back(1);
        }
        else {
          for (size_t j = 0; j < currentVarData.varDims.size(); j++) {
            currentVarData.writeStarts.push_back(0);
            currentVarData.writeCounts.push_back(dimLens[currentVarData.varDims[j]]);
          }
        }

        // Get variable size
        currentVarData.sz = 1;
        for (std::size_t idx = 0; idx != currentVarData.writeCounts.size(); idx++)
          currentVarData.sz *= currentVarData.writeCounts[idx];
      }
    } // if (WriteNC::ENTLOCSET == currentVarData.entLoc)
    // Process non-set variables
    else {
      Tag indexedTag = 0;

      if (currentVarData.has_tsteps) {
        for (unsigned int t = 0; t < tstep_nums.size(); t++) {
          std::stringstream ssTagNameWithIndex;
          ssTagNameWithIndex << varname << tstep_nums[t];
          rval = mbImpl->tag_get_handle(ssTagNameWithIndex.str().c_str(), indexedTag);MB_CHK_SET_ERR(rval, "Can't find tag " << ssTagNameWithIndex.str());
          dbgOut.tprintf(2, "    found indexed tag %d with name %s\n", tstep_nums[t], ssTagNameWithIndex.str().c_str());
          currentVarData.varTags.push_back(indexedTag);
        }
      }
      else {
        // This should be a user-created non-set variable without timesteps
        // Treat it like having one, 0th, timestep
        std::stringstream ssTagNameWithIndex;
        ssTagNameWithIndex << varname << 0;
        rval = mbImpl->tag_get_handle(ssTagNameWithIndex.str().c_str(), indexedTag);MB_CHK_SET_ERR(rval, "Can't find tag " << ssTagNameWithIndex.str() << " for a user-created variable");
        dbgOut.tprintf(2, "    found indexed tag 0 with name %s\n", ssTagNameWithIndex.str().c_str());
        currentVarData.varTags.push_back(indexedTag);
      }

      // The type of the tag is fixed though
      DataType type;
      rval = mbImpl->tag_get_data_type(indexedTag, type);MB_CHK_SET_ERR(rval, "Can't get data type of tag " << varname);

      currentVarData.varDataType = NC_DOUBLE;
      if (MB_TYPE_INTEGER == type)
        currentVarData.varDataType = NC_INT;
    }
  } // for (size_t i = 0; i < var_names.size(); i++)

  // Process coordinate variables here
  // Check that for used coordinates we have found the tags
  for (std::set<std::string>::iterator setIt = usedCoordinates.begin();
      setIt != usedCoordinates.end(); ++setIt) {
    const std::string& coordName = *setIt;

    std::map<std::string, WriteNC::VarData>::iterator vit = varInfo.find(coordName);
    if (vit == varInfo.end())
      MB_SET_ERR(MB_FAILURE, "Can't find coordinate variable " << coordName);

    WriteNC::VarData& varCoordData = vit->second;
    Tag coordTag = 0;
    rval = mbImpl->tag_get_handle(coordName.c_str(), coordTag);MB_CHK_SET_ERR(rval, "Can't find tag " << coordName);
    varCoordData.varTags.push_back(coordTag); // Really, only one for these

    const void* data;
    int sizeCoordinate;
    rval = mbImpl->tag_get_by_ptr(coordTag, &_fileSet, 1, &data, &sizeCoordinate);MB_CHK_SET_ERR(rval, "Can't get coordinate values of " << coordName);
    dbgOut.tprintf(2, "    found coordinate tag with name %s and length %d\n", coordName.c_str(),
        sizeCoordinate);

    // Find the type of tag, and use it
    DataType type;
    rval = mbImpl->tag_get_data_type(coordTag, type);MB_CHK_SET_ERR(rval, "Can't get data type of tag " << coordName);
    varCoordData.varDataType = NC_DOUBLE;
    if (MB_TYPE_INTEGER == type)
      varCoordData.varDataType = NC_INT;

    // Get dimension length (the only dimension of this coordinate variable, with the same name)
    assert(1 == varCoordData.varDims.size());
    int coordDimLen = dimLens[varCoordData.varDims[0]];

    if (dummyVarNames.find(coordName) != dummyVarNames.end()) {
      // For a dummy coordinate variable, the tag size is always 1
      // The number of coordinates should be set to dimension length, instead of 1
      assert(1 == sizeCoordinate);
      sizeCoordinate = coordDimLen;

      // No variable data to write
      data = NULL;
    }
    else {
      // The number of coordinates should be exactly the same as dimension length
      // However, if timesteps spread across files and time tag has been updated,
      // sizeCoordinate will be larger
      if (varCoordData.varDims[0] != tDim)
        assert(sizeCoordinate == coordDimLen);
    }

    // For time, the actual output size and values are determined by tstep_nums
    if (varCoordData.varDims[0] == tDim) {
      // Does not apply to dummy time tag (e.g. 'Time' tag of MPAS), when timesteps
      // spread across files
      if (NULL != data)
        assert(tstep_nums.size() > 0 && tstep_nums.size() <= (size_t)sizeCoordinate);

      sizeCoordinate = tstep_nums.size();

      if (NULL != data) {
        assert(NC_DOUBLE == varCoordData.varDataType);
        timeStepVals.resize(sizeCoordinate);
        for (unsigned int t = 0; t < tstep_nums.size(); t++)
          timeStepVals[t] = ((double*)data)[tstep_nums[t]];

        data = &timeStepVals[0];
      }
    }

    // This is the length
    varCoordData.sz = sizeCoordinate;
    varCoordData.writeStarts.resize(1);
    varCoordData.writeStarts[0] = 0;
    varCoordData.writeCounts.resize(1);
    varCoordData.writeCounts[0] = sizeCoordinate;

    assert(0 == varCoordData.memoryHogs.size()); // Nothing so far
    varCoordData.memoryHogs.push_back((void*)data);
  } // for (std::set<std::string>::iterator setIt ...

  return MB_SUCCESS;
}

ErrorCode NCWriteHelper::init_file(std::vector<std::string>& var_names, std::vector<std::string>& desired_names, bool append)
{
  std::vector<std::string>& dimNames = _writeNC->dimNames;
  std::set<std::string>& usedCoordinates = _writeNC->usedCoordinates;
  std::set<std::string>& dummyVarNames = _writeNC->dummyVarNames;
  std::map<std::string, WriteNC::VarData>& varInfo = _writeNC->varInfo;
  std::map<std::string, WriteNC::AttData>& globalAtts = _writeNC->globalAtts;
  DebugOutput& dbgOut = _writeNC->dbgOut;

  int tDim_in_dimNames = tDim;
  int levDim_in_dimNames = levDim;

  // If append mode, make sure we are in define mode; a simple open will not allow creation of new variables
  if (append) {
    int errcode = NCFUNC(redef)(_fileId);
    if (errcode != NC_NOERR)
      MB_SET_ERR(MB_FAILURE, "Can't open file in redefine mode");
  }

  // First initialize all coordinates, then fill VarData for actual variables (and dimensions)
  // Check that for used coordinates we have found the tags
  for (std::set<std::string>::iterator setIt = usedCoordinates.begin();
      setIt != usedCoordinates.end(); ++setIt) {
    const std::string& coordName = *setIt;

    std::map<std::string, WriteNC::VarData>::iterator vit = varInfo.find(coordName);
    if (vit == varInfo.end())
      MB_SET_ERR(MB_FAILURE, "Can't find coordinate variable " << coordName);

    WriteNC::VarData& varCoordData = vit->second;
    varCoordData.varDims.resize(1);

    // If not append, create it for sure
    // If append, we might already have it, including the tag / variable with the same name
    /*
     * int ncmpi_inq_dimid(int ncid, const char *name, int *idp);
     */
    if (append) {
      int dimId;
      if (NCFUNC(inq_dimid)(_fileId, coordName.c_str(), &dimId) == NC_NOERR) { // If not found, create it later
        varCoordData.varDims[0] = dimId;
        dbgOut.tprintf(2, "    file already has coordName %s dim id is %d \n", coordName.c_str(), (int)varCoordData.varDims[0]);

        // Update tDim and levDim to actual dimension id
        if (coordName == dimNames[tDim_in_dimNames])
          tDim = varCoordData.varDims[0];
        else if (coordName == dimNames[levDim_in_dimNames])
          levDim = varCoordData.varDims[0];

        // Skip dummy coordinate variables (e.g. ncol)
        if (dummyVarNames.find(coordName) != dummyVarNames.end())
          continue;

        // Check that the coordinate is a variable too
        // Inquire for a variable with the same name
        int varId;
        if (NCFUNC(inq_varid)(_fileId, coordName.c_str(), &varId) != NC_NOERR)
          MB_SET_ERR(MB_FAILURE, "We do not have a variable with the same name " << coordName);
        // We should also check that this variable has one dimension, and it is dimId
        varCoordData.varId = varId;
        dbgOut.tprintf(2, "    file already has coordinate %s and varId is %d \n", coordName.c_str(), varId);

        continue; // Maybe more checks are needed here
      }
    }

    /* int nc_def_dim (int ncid, const char *name, size_t len, int *dimidp);
       * example:  status = nc_def_dim(fileId, "lat", 18L, &latid);
    */

    // Actually define a dimension
    if (NCFUNC(def_dim)(_fileId, coordName.c_str(), (size_t)varCoordData.sz,
        &varCoordData.varDims[0]) != NC_NOERR)
      MB_SET_ERR(MB_FAILURE, "Failed to generate dimension " << coordName);
    dbgOut.tprintf(2, "    for coordName %s dim id is %d \n", coordName.c_str(), (int)varCoordData.varDims[0]);

    // Update tDim and levDim to actual dimension id
    if (coordName == dimNames[tDim_in_dimNames])
      tDim = varCoordData.varDims[0];
    else if (coordName == dimNames[levDim_in_dimNames])
      levDim = varCoordData.varDims[0];

    // Create a variable with the same name, and its only dimension the one we just defined
    /*
     * int nc_def_var (int ncid, const char *name, nc_type xtype,
                       int ndims, const int dimids[], int *varidp);
       example: http://www.unidata.ucar.edu/software/netcdf/docs/netcdf-c/nc_005fdef_005fvar.html#nc_005fdef_005fvar
     */

    // Skip dummy coordinate variables (e.g. ncol)
    if (dummyVarNames.find(coordName) != dummyVarNames.end())
      continue;

    // Define a coordinate variable
    if (NCFUNC(def_var)(_fileId, coordName.c_str(), varCoordData.varDataType,
        1, &(varCoordData.varDims[0]), &varCoordData.varId) != NC_NOERR)
      MB_SET_ERR(MB_FAILURE, "Failed to create coordinate variable " << coordName);

    dbgOut.tprintf(2, "    for coordName %s variable id is %d \n", coordName.c_str(), varCoordData.varId);
  }

  // Now look at requested variables, and update from the index in dimNames to the actual dimension id
  for (size_t i = 0; i < var_names.size(); i++) {
    std::map<std::string, WriteNC::VarData>::iterator vit = varInfo.find(var_names[i]);
    if (vit == varInfo.end())
      MB_SET_ERR(MB_FAILURE, "Can't find requested variable " << var_names[i]);

    // Skip coordinate variables
    if (usedCoordinates.find(var_names[i]) != usedCoordinates.end())
      continue;

    WriteNC::VarData& variableData = vit->second;

    // The index is for dimNames; we need to find out the actual dimension id (from above)
    int numDims = (int)variableData.varDims.size();
    for (int j = 0; j < numDims; j++) {
      std::string dimName = dimNames[variableData.varDims[j]];
      std::map<std::string, WriteNC::VarData>::iterator vit2 = varInfo.find(dimName);
      if (vit2 == varInfo.end())
        MB_SET_ERR(MB_FAILURE, "Can't find requested coordinate variable " << dimName);

      WriteNC::VarData& coordData = vit2->second;
      // Index in dimNames to actual dimension id
      variableData.varDims[j] = coordData.varDims[0]; // This one, being a coordinate, is the only one
      dbgOut.tprintf(2, "          dimension with index %d name %s has ID %d \n",
          j, dimName.c_str(), variableData.varDims[j]);
    }

    // Define the variable now:
    int errCode = NCFUNC(def_var)(_fileId, desired_names[i].c_str(), variableData.varDataType,
        (int)variableData.varDims.size(), &(variableData.varDims[0]),
        &variableData.varId);
    if (errCode != NC_NOERR)
      MB_SET_ERR(MB_FAILURE, "Failed to create requested variable " << desired_names[i]);

    dbgOut.tprintf(2, "    for variable %s with desired name %s variable id is %d \n", var_names[i].c_str(),
        desired_names[i].c_str(), variableData.varId);
    // Now define the variable, with all dimensions
  }

  // Define global attributes (exactly copied from the original file for the time being)
  // Should we modify some of them (e.g. revision_Id) later?
  std::map<std::string, WriteNC::AttData>::iterator attIt;
  for (attIt = globalAtts.begin(); attIt != globalAtts.end(); ++attIt) {
    const std::string& attName = attIt->first;
    WriteNC::AttData& attData = attIt->second;
    NCDF_SIZE& attLen = attData.attLen;
    nc_type& attDataType = attData.attDataType;
    const std::string& attValue = attData.attValue;

    switch (attDataType) {
      case NC_BYTE:
      case NC_CHAR:
        if (NC_NOERR != NCFUNC(put_att_text)(_fileId, NC_GLOBAL, attName.c_str(), attLen, attValue.c_str()))
          MB_SET_ERR(MB_FAILURE, "Failed to define text type attribute");
        break;
      case NC_DOUBLE:
        if (NC_NOERR != NCFUNC(put_att_double)(_fileId, NC_GLOBAL, attName.c_str(), NC_DOUBLE, 1, (double*)attValue.c_str()))
          MB_SET_ERR(MB_FAILURE, "Failed to define double type attribute");
        break;
      case NC_FLOAT:
        if (NC_NOERR != NCFUNC(put_att_float)(_fileId, NC_GLOBAL, attName.c_str(), NC_FLOAT, 1, (float*)attValue.c_str()))
          MB_SET_ERR(MB_FAILURE, "Failed to define float type attribute");
        break;
      case NC_INT:
        if (NC_NOERR != NCFUNC(put_att_int)(_fileId, NC_GLOBAL, attName.c_str(), NC_INT, 1, (int*)attValue.c_str()))
          MB_SET_ERR(MB_FAILURE, "Failed to define int type attribute");
        break;
      case NC_SHORT:
        if (NC_NOERR != NCFUNC(put_att_short)(_fileId, NC_GLOBAL, attName.c_str(), NC_SHORT, 1, (short*)attValue.c_str()))
          MB_SET_ERR(MB_FAILURE, "Failed to define short type attribute");
        break;
      default:
        MB_SET_ERR(MB_FAILURE, "Unknown attribute data type");
    }
  }

  // Take it out of define mode
  if (NC_NOERR != NCFUNC(enddef)(_fileId))
    MB_SET_ERR(MB_FAILURE, "Failed to close define mode");

  return MB_SUCCESS;
}

ErrorCode NCWriteHelper::write_values(std::vector<std::string>& var_names, std::vector<int>& tstep_nums)
{
  std::set<std::string>& usedCoordinates = _writeNC->usedCoordinates;
  std::set<std::string>& dummyVarNames = _writeNC->dummyVarNames;
  std::map<std::string, WriteNC::VarData>& varInfo = _writeNC->varInfo;

  std::vector<WriteNC::VarData> vdatas;
  std::vector<WriteNC::VarData> vsetdatas;

  // For set variables, include coordinates used by requested var_names
  for (std::set<std::string>::iterator setIt = usedCoordinates.begin();
      setIt != usedCoordinates.end(); ++setIt) {
    const std::string& coordName = *setIt;

    // Skip dummy coordinate variables (if any)
    if (dummyVarNames.find(coordName) != dummyVarNames.end())
      continue;

    std::map<std::string, WriteNC::VarData>::iterator vit = varInfo.find(coordName);
    if (vit == varInfo.end()) {
      MB_SET_ERR(MB_FAILURE, "Can't find coordinate variable " << coordName);
    }

     vsetdatas.push_back(vit->second);
  }

  // Collect non-set and set variables from requested var_names
  for (unsigned int i = 0; i < var_names.size(); i++) {
    std::map<std::string, WriteNC::VarData>::iterator vit = varInfo.find(var_names[i]);
    if (vit == varInfo.end()) {
      MB_SET_ERR(MB_FAILURE, "Can't find requested variable " << var_names[i]);
    }

    WriteNC::VarData& variableData = vit->second;
    if (WriteNC::ENTLOCSET == variableData.entLoc) {
      // Used coordinates has all ready been included
      if (usedCoordinates.find(var_names[i]) != usedCoordinates.end())
        continue;

      vsetdatas.push_back(variableData);
    }
    else
      vdatas.push_back(variableData);
  }

  // Assume that the data ranges do not overlap across processors
  // While overlapped writing might still work, we should better not take that risk
  write_nonset_variables(vdatas, tstep_nums);

  // Use independent I/O mode put, since this write is only for the root processor
  write_set_variables(vsetdatas, tstep_nums);

  return MB_SUCCESS;
}

ErrorCode NCWriteHelper::write_set_variables(std::vector<WriteNC::VarData>& vsetdatas, std::vector<int>& /* tstep_nums */)
{
  int success;

// CAUTION: if the NetCDF ID is from a previous call to ncmpi_create rather than ncmpi_open,
// all processors need to call ncmpi_begin_indep_data(). If only the root processor does so,
// ncmpi_begin_indep_data() call will be blocked forever :(
 #ifdef MOAB_HAVE_PNETCDF
   // Enter independent I/O mode
   success = NCFUNC(begin_indep_data)(_fileId);
   if (success)
     MB_SET_ERR(MB_FAILURE, "Failed to begin independent I/O mode");
 #endif

   int rank = 0;
 #ifdef MOAB_HAVE_MPI
   bool& isParallel = _writeNC->isParallel;
   if (isParallel) {
     ParallelComm*& myPcomm = _writeNC->myPcomm;
     rank = myPcomm->proc_config().proc_rank();
   }
 #endif
   if (0 == rank) {
     for (unsigned int i = 0; i < vsetdatas.size(); i++) {
       WriteNC::VarData& variableData = vsetdatas[i];

       // Set variables with timesteps, e.g. xtime(Time) or xtime(Time, StrLen)
       if (variableData.has_tsteps) {
         MB_SET_ERR(MB_NOT_IMPLEMENTED, "Writing set variables with timesteps is not implemented yet");
       }

       switch (variableData.varDataType) {
         case NC_DOUBLE:
           // Independent I/O mode put
           success = NCFUNCP(_vara_double)(_fileId, variableData.varId, &variableData.writeStarts[0],
                     &variableData.writeCounts[0], (double*)(variableData.memoryHogs[0]));
           if (success)
             MB_SET_ERR(MB_FAILURE, "Failed to write double data for variable " << variableData.varName);
           break;
         case NC_INT:
           // Independent I/O mode put
           success = NCFUNCP(_vara_int)(_fileId, variableData.varId, &variableData.writeStarts[0],
                     &variableData.writeCounts[0], (int*)(variableData.memoryHogs[0]));
           if (success)
             MB_SET_ERR(MB_FAILURE, "Failed to write int data for variable " << variableData.varName);
           break;
         default:
           MB_SET_ERR(MB_NOT_IMPLEMENTED, "Writing non-double or non-int data is not implemented yet");
       }
     }
   }

 #ifdef MOAB_HAVE_PNETCDF
   // End independent I/O mode
   success = NCFUNC(end_indep_data)(_fileId);
   if (success)
     MB_SET_ERR(MB_FAILURE, "Failed to end independent I/O mode");
 #endif

   return MB_SUCCESS;
}

ErrorCode ScdNCWriteHelper::collect_mesh_info()
{
  Interface*& mbImpl = _writeNC->mbImpl;
  std::vector<std::string>& dimNames = _writeNC->dimNames;
  std::vector<int>& dimLens = _writeNC->dimLens;

  ErrorCode rval;

  // Look for time dimension
  std::vector<std::string>::iterator vecIt;
  if ((vecIt = std::find(dimNames.begin(), dimNames.end(), "time")) != dimNames.end())
    tDim = vecIt - dimNames.begin();
  else if ((vecIt = std::find(dimNames.begin(), dimNames.end(), "t")) != dimNames.end())
    tDim = vecIt - dimNames.begin();
  else {
    MB_SET_ERR(MB_FAILURE, "Couldn't find 'time' or 't' dimension");
  }
  nTimeSteps = dimLens[tDim];

  // Get number of levels
  if ((vecIt = std::find(dimNames.begin(), dimNames.end(), "lev")) != dimNames.end())
    levDim = vecIt - dimNames.begin();
  else if ((vecIt = std::find(dimNames.begin(), dimNames.end(), "ilev")) != dimNames.end())
    levDim = vecIt - dimNames.begin();
  else {
    MB_SET_ERR(MB_FAILURE, "Couldn't find 'lev' or 'ilev' dimension");
  }
  nLevels = dimLens[levDim];

  // __<dim_name>_LOC_MINMAX (for slon, slat, lon and lat)
  Tag convTag = 0;
  rval = mbImpl->tag_get_handle("__slon_LOC_MINMAX", 0, MB_TYPE_INTEGER, convTag,
                                MB_TAG_ANY);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag __slon_LOC_MINMAX");
  int val[2];
  rval = mbImpl->tag_get_data(convTag, &_fileSet, 1, val);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag __slon_LOC_MINMAX");
  lDims[0] = val[0];
  lDims[3] = val[1];

  rval = mbImpl->tag_get_handle("__slat_LOC_MINMAX", 0, MB_TYPE_INTEGER, convTag,
                                MB_TAG_ANY);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag __slat_LOC_MINMAX");
  rval = mbImpl->tag_get_data(convTag, &_fileSet, 1, val);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag __slat_LOC_MINMAX");
  lDims[1] = val[0];
  lDims[4] = val[1];

  rval = mbImpl->tag_get_handle("__lon_LOC_MINMAX", 0, MB_TYPE_INTEGER, convTag,
                                MB_TAG_ANY);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag __lon_LOC_MINMAX");
  rval = mbImpl->tag_get_data(convTag, &_fileSet, 1, val);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag __lon_LOC_MINMAX");
  lCDims[0] = val[0];
  lCDims[3] = val[1];

  rval = mbImpl->tag_get_handle("__lat_LOC_MINMAX", 0, MB_TYPE_INTEGER, convTag,
                                MB_TAG_ANY);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag __lat_LOC_MINMAX");
  rval = mbImpl->tag_get_data(convTag, &_fileSet, 1, val);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag __lat_LOC_MINMAX");
  lCDims[1] = val[0];
  lCDims[4] = val[1];

  // Get local faces
  rval = mbImpl->get_entities_by_dimension(_fileSet, 2, localCellsOwned);MB_CHK_SET_ERR(rval, "Trouble getting local faces in current file set");
  assert(!localCellsOwned.empty());

#ifdef MOAB_HAVE_MPI
  bool& isParallel = _writeNC->isParallel;
  if (isParallel) {
    ParallelComm*& myPcomm = _writeNC->myPcomm;
    int procs = myPcomm->proc_config().proc_size();
    if (procs > 1) {
      rval = myPcomm->filter_pstatus(localCellsOwned, PSTATUS_NOT_OWNED, PSTATUS_NOT);MB_CHK_SET_ERR(rval, "Trouble getting owned faces in current file set");
    }
  }
#endif

  return MB_SUCCESS;
}

ErrorCode ScdNCWriteHelper::collect_variable_data(std::vector<std::string>& var_names, std::vector<int>& tstep_nums)
{
  NCWriteHelper::collect_variable_data(var_names, tstep_nums);

  std::map<std::string, WriteNC::VarData>& varInfo = _writeNC->varInfo;

  for (size_t i = 0; i < var_names.size(); i++) {
    std::string varname = var_names[i];
    std::map<std::string, WriteNC::VarData>::iterator vit = varInfo.find(varname);
    if (vit == varInfo.end())
      MB_SET_ERR(MB_FAILURE, "Can't find variable " << varname);

    WriteNC::VarData& currentVarData = vit->second;
#ifndef NDEBUG
    std::vector<int>& varDims = currentVarData.varDims;
#endif

    // Skip set variables, which were already processed in NCWriteHelper::collect_variable_data()
    if (WriteNC::ENTLOCSET == currentVarData.entLoc)
      continue;

    // Set up writeStarts and writeCounts (maximum number of dimensions is 4)
    currentVarData.writeStarts.resize(4);
    currentVarData.writeCounts.resize(4);
    unsigned int dim_idx = 0;

    // First: time
    if (currentVarData.has_tsteps) {
      // Non-set variables with timesteps
      // 4 dimensions like (time, lev, lat, lon)
      // 3 dimensions like (time, lat, lon)
      assert(4 == varDims.size() || 3 == varDims.size());

      // Time should be the first dimension
      assert(tDim == varDims[0]);

      currentVarData.writeStarts[dim_idx] = 0; // This value is timestep dependent, will be set later
      currentVarData.writeCounts[dim_idx] = 1;
      dim_idx++;
    }
    else {
      // Non-set variables without timesteps
      // 3 dimensions like (lev, lat, lon)
      // 2 dimensions like (lat, lon)
      assert(3 == varDims.size() || 2 == varDims.size());
    }

    // Next: lev
    if (currentVarData.numLev > 0) {
      // Non-set variables with levels
      // 4 dimensions like (time, lev, lat, lon)
      // 3 dimensions like (lev, lat, lon)
      assert(4 == varDims.size() || 3 == varDims.size());

      currentVarData.writeStarts[dim_idx] = 0;
      currentVarData.writeCounts[dim_idx] = currentVarData.numLev;
      dim_idx++;
    }
    else {
      // Non-set variables without levels
      // 3 dimensions like (time, lat, lon)
      // 2 dimensions like (lat, lon)
      assert(3 == varDims.size() || 2 == varDims.size());
    }

    // Finally: lat and lon
    switch (currentVarData.entLoc) {
      case WriteNC::ENTLOCFACE:
        // Faces
        currentVarData.writeStarts[dim_idx] = lCDims[1];
        currentVarData.writeCounts[dim_idx] = lCDims[4] - lCDims[1] + 1;
        currentVarData.writeStarts[dim_idx + 1] = lCDims[0];
        currentVarData.writeCounts[dim_idx + 1] = lCDims[3] - lCDims[0] + 1;
        break;
      default:
        MB_SET_ERR(MB_FAILURE, "Unexpected entity location type for variable " << varname);
    }
    dim_idx += 2;

    // Get variable size
    currentVarData.sz = 1;
    for (std::size_t idx = 0; idx < dim_idx; idx++)
      currentVarData.sz *= currentVarData.writeCounts[idx];
  } // for (size_t i = 0; i < var_names.size(); i++)

  return MB_SUCCESS;
}

// Write CAM-EUL and CAM-FV non-set variables on non-shared quads (e.g. T)
// We assume that there are no variables on vertices and we do not support
// variables on edges (e.g. US in CAM-FV) for the time being
ErrorCode ScdNCWriteHelper::write_nonset_variables(std::vector<WriteNC::VarData>& vdatas, std::vector<int>& tstep_nums)
{
  Interface*& mbImpl = _writeNC->mbImpl;

  int success;

  // For each indexed variable tag, write a time step data
  for (unsigned int i = 0; i < vdatas.size(); i++) {
    WriteNC::VarData& variableData = vdatas[i];

    // Assume this variable is on faces for the time being
    switch (variableData.entLoc) {
      case WriteNC::ENTLOCFACE:
        // Faces
        break;
      default:
        MB_SET_ERR(MB_FAILURE, "Unexpected entity location type for variable " << variableData.varName);
    }

    unsigned int num_timesteps;
    unsigned int lat_idx = 0;
    unsigned int lon_idx = 1;
    if (variableData.has_tsteps) {
      // Non-set variables with timesteps
      // 4 dimensions like (time, lev, lat, lon)
      // 3 dimensions like (time, lat, lon)
      num_timesteps = tstep_nums.size();
      lat_idx++;
      lon_idx++;
    }
    else {
      // Non-set variables without timesteps
      // 3 dimensions like (lev, lat, lon)
      // 2 dimensions like (lat, lon)
      num_timesteps = 1;
    }

    unsigned int num_lev;
    if (variableData.numLev > 0) {
      // Non-set variables with levels
      // 4 dimensions like (time, lev, lat, lon)
      // 3 dimensions like (lev, lat, lon)
      num_lev = variableData.numLev;
      lat_idx++;
      lon_idx++;
    }
    else {
      // Non-set variables without levels
      // 3 dimensions like (time, lat, lon)
      // 2 dimensions like (lat, lon)
      num_lev = 1;
    }

    size_t ni = variableData.writeCounts[lon_idx]; // lon
    size_t nj = variableData.writeCounts[lat_idx]; // lat

    // At each timestep, we need to transpose tag format (lat, lon, lev) back
    // to NC format (lev, lat, lon) for writing
    for (unsigned int t = 0; t < num_timesteps; t++) {
      // We will write one time step, and count will be one; start will be different
      // Use tag_iterate to get tag data (assume that localCellsOwned is contiguous)
      // We should also transpose for level so that means deep copy for transpose
      if (tDim == variableData.varDims[0])
        variableData.writeStarts[0] = t; // This is start for time
      int count;
      void* dataptr;
      ErrorCode rval = mbImpl->tag_iterate(variableData.varTags[t], localCellsOwned.begin(), localCellsOwned.end(),
                                           count, dataptr);MB_CHK_SET_ERR(rval, "Failed to iterate tag on owned faces");
      assert(count == (int)localCellsOwned.size());

      // Now transpose and write tag data
      // Use collective I/O mode put (synchronous write) for the time being, we can try
      // nonblocking put (request aggregation) later
      switch (variableData.varDataType) {
        case NC_DOUBLE: {
          std::vector<double> tmpdoubledata(ni*nj*num_lev);
          if (num_lev > 1)
            // Transpose (lat, lon, lev) back to (lev, lat, lon)
            jik_to_kji(ni, nj, num_lev, &tmpdoubledata[0], (double*)(dataptr));
          success = NCFUNCAP(_vara_double)(_fileId, variableData.varId,
                    &variableData.writeStarts[0], &variableData.writeCounts[0],
                    &tmpdoubledata[0]);
          if (success)
            MB_SET_ERR(MB_FAILURE, "Failed to write double data for variable " << variableData.varName);
          break;
        }
        default:
          MB_SET_ERR(MB_NOT_IMPLEMENTED, "Writing non-double data is not implemented yet");
      }
    }
  }

  return MB_SUCCESS;
}

} /* namespace moab */
