/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkEnSightWriter.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/

/*----------------------------------------------------------------------------
 Copyright (c) Sandia Corporation
 See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details.
----------------------------------------------------------------------------*/

/* TODO
 *
 *
 * check that data was written
 *
 */

#include "vtkEnSightWriter.h"

#include <vtksys/SystemTools.hxx>

#include "vtkToolkits.h" // for VTK_USE_PARALLEL
#ifdef VTK_USE_PARALLEL
#include "vtkMultiProcessController.h"
#endif

#include "vtkBitArray.h"
#include "vtkByteSwap.h"
#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkCharArray.h"
#include "vtkDataSet.h"
#include "vtkDoubleArray.h"
#include "vtkErrorCode.h"
#include "vtkFieldData.h"
#include "vtkFloatArray.h"
#include "vtkInformation.h"
#include "vtkIntArray.h"
#include "vtkLongArray.h"
#include "vtkLookupTable.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkShortArray.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkUnsignedCharArray.h"
#include "vtkUnsignedIntArray.h"
#include "vtkUnsignedLongArray.h"
#include "vtkUnsignedShortArray.h"

#include "vtkPriorityQueue.h"

#include "vtkUnstructuredGrid.h"

#include <cctype>
#include <cerrno>
#include <cmath>

#include <algorithm>
#include <list>
#include <map>
#include <vector>

// If we are building against a slightly older VTK version,
// these cell types are not defined, and won't occur in the input

#ifndef VTK_QUADRATIC_WEDGE
#define VTK_QUADRATIC_WEDGE 26
#define VTK_QUADRATIC_PYRAMID 27
#endif

//------------------------------------------------------------------------------
vtkStandardNewMacro(vtkEnSightWriter);

//------------------------------------------------------------------------------
// Created object with no filename and timestep 0
vtkEnSightWriter::vtkEnSightWriter()
{

  this->BaseName = nullptr;
  this->FileName = nullptr;
  this->TimeStep = 0;
  this->Path = nullptr;
  this->GhostLevelMultiplier = 10000;
  this->GhostLevel = 0;
  this->TransientGeometry = false;
  this->ProcessNumber = 0;
  this->NumberOfProcesses = 1;
  this->NumberOfBlocks = 0;
  this->BlockIDs = nullptr;
  this->TmpInput = nullptr;
}

//------------------------------------------------------------------------------
vtkEnSightWriter::~vtkEnSightWriter()
{
  this->SetBaseName(nullptr);
  this->SetFileName(nullptr);
  this->SetPath(nullptr);
}

//------------------------------------------------------------------------------
void vtkEnSightWriter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "FileName: " << (this->FileName ? this->FileName : "(none)") << "\n";
  os << indent << "Path: " << (this->Path ? this->Path : "(none)") << "\n";
  os << indent << "BaseName: " << (this->BaseName ? this->BaseName : "(none)") << "\n";

  os << indent << "TimeStep: " << this->TimeStep << "\n";
  os << indent << "TransientGeometry: " << this->TransientGeometry << "\n";
  os << indent << "ProcessNumber: " << this->ProcessNumber << endl;
  os << indent << "NumberOfProcesses: " << this->NumberOfProcesses << endl;
  os << indent << "NumberOfBlocks: " << this->NumberOfBlocks << endl;
  os << indent << "BlockIDs: " << this->BlockIDs << endl;
  os << indent << "GhostLevel: " << this->GhostLevel << endl;
}

int vtkEnSightWriter::FillInputPortInformation(int vtkNotUsed(port), vtkInformation* info)
{
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkUnstructuredGrid");
  return 1;
}

//------------------------------------------------------------------------------
// Specify the input data or filter.
void vtkEnSightWriter::SetInputData(vtkUnstructuredGrid* input)
{
  this->SetInputDataInternal(0, input);
}

//------------------------------------------------------------------------------
// Specify the input data or filter.
vtkUnstructuredGrid* vtkEnSightWriter::GetInput()
{
  if (this->GetNumberOfInputConnections(0) < 1)
  {
    return nullptr;
  }
  else if (this->TmpInput)
  {
    return this->TmpInput;
  }
  else
  {
    return (vtkUnstructuredGrid*)(this->Superclass::GetInput());
  }
}

//------------------------------------------------------------------------------
void vtkEnSightWriter::WriteData()
{
  int i;
  unsigned int ui;
  int blockCount = 0;
  std::list<int>::iterator iter;

  if (this->TmpInput)
  {
    this->TmpInput->Delete();
    this->TmpInput = nullptr;
  }

  // figure out process ID

  this->ProcessNumber = 0;
  this->NumberOfProcesses = 1;

#ifdef VTK_USE_PARALLEL
  vtkMultiProcessController* c = vtkMultiProcessController::GetGlobalController();

  if (c != nullptr)
  {
    this->ProcessNumber = c->GetLocalProcessId();
    this->NumberOfProcesses = c->GetNumberOfProcesses();
  }
#endif

  vtkUnstructuredGrid* input = this->GetInput();
  vtkInformation* inInfo = this->GetInputInformation();

  if (this->GhostLevel >
    inInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS()))
  {
    // re-execute pipeline if necessary to obtain ghost cells

    this->GetInputAlgorithm()->UpdateInformation();
    inInfo->Set(
      vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS(), this->GhostLevel);
    this->GetInputAlgorithm()->Update();
  }

  // get the BlockID Cell Array
  vtkDataArray* BlockData = input->GetCellData()->GetScalars("BlockId");

  if (BlockData == nullptr || strcmp(BlockData->GetName(), "BlockId") != 0)
  {
    BlockData = nullptr;
  }

  this->ComputeNames();

  if (!this->BaseName)
  {
    vtkErrorMacro("A FileName or Path/BaseName must be specified.");
    return;
  }

  this->SanitizeFileName(this->BaseName);

  char** blockNames = nullptr;
  int* elementIDs = nullptr;
  char charBuffer[1024];
  char fileBuffer[512];
  snprintf(charBuffer, sizeof(charBuffer), "%s/%s.%d.%05d.geo", this->Path, this->BaseName,
    this->ProcessNumber, this->TimeStep);

  // open the geometry file
  // only if timestep 0 and not transient geometry or transient geometry
  FILE* fd = nullptr;
  if (this->ShouldWriteGeometry())
  {
    if (!(fd = OpenFile(charBuffer)))
      return;
  }

  // Get the FILE's for Point Data Fields
  std::vector<FILE*> pointArrayFiles;
  int NumPointArrays = input->GetPointData()->GetNumberOfArrays();
  for (i = 0; i < NumPointArrays; i++)
  {
    strcpy(fileBuffer, input->GetPointData()->GetArray(i)->GetName());
    this->SanitizeFileName(fileBuffer);
    snprintf(charBuffer, sizeof(charBuffer), "%s/%s.%d.%05d_n.%s", this->Path, this->BaseName,
      this->ProcessNumber, this->TimeStep, fileBuffer);
    FILE* ftemp = OpenFile(charBuffer);
    if (!ftemp)
    {
      fclose(fd);
      return;
    }
    pointArrayFiles.push_back(ftemp);

    // write the description line to the file
    WriteStringToFile(fileBuffer, ftemp);
  }

  // Get the FILE's for Cell Data Fields
  std::vector<FILE*> cellArrayFiles;
  int NumCellArrays = input->GetCellData()->GetNumberOfArrays();
  for (i = 0; i < NumCellArrays; i++)
  {

    strcpy(fileBuffer, input->GetCellData()->GetArray(i)->GetName());
    this->SanitizeFileName(fileBuffer);
    snprintf(charBuffer, sizeof(charBuffer), "%s/%s.%d.%05d_c.%s", this->Path, this->BaseName,
      this->ProcessNumber, this->TimeStep, fileBuffer);
    FILE* ftemp = OpenFile(charBuffer);
    if (!ftemp)
    {
      fclose(fd);
      return;
    }
    cellArrayFiles.push_back(ftemp);

    // write the description line to the file
    WriteStringToFile(fileBuffer, ftemp);
  }

  // write the header information
  if (this->ShouldWriteGeometry())
  {
    this->WriteStringToFile("C Binary", fd);
    this->WriteStringToFile("Written by VTK EnSight Writer", fd);
    // if (this->Title)
    // this->WriteStringToFile(this->Title,fd);
    // else
    this->WriteStringToFile("No Title was Specified", fd);
    // we will specify node and element ID's
    this->WriteStringToFile("node id given\n", fd);
    this->WriteStringToFile("element id given\n", fd);
  }

  // get the Ghost Cell Array if it exists
  vtkDataArray* GhostData =
    input->GetCellData()->GetScalars(vtkDataSetAttributes::GhostArrayName());
  // if the strings are not the same then we did not get the ghostData array
  if (GhostData == nullptr ||
    strcmp(GhostData->GetName(), vtkDataSetAttributes::GhostArrayName()) != 0)
  {
    GhostData = nullptr;
  }

  // data structure to get all the cells for a certain part
  // basically sort by part# and cell type
  std::map<int, std::vector<int>> CellsByPart;

  // just a list of part numbers
  std::list<int> partNumbers;

  // get all the part numbers in the unstructured grid and sort the cells
  // by part number
  for (i = 0; i < input->GetNumberOfCells(); i++)
  {
    int key = 1;
    if (BlockData)
      key = (int)(BlockData->GetTuple(i)[0]);
    else
      cout << "No BlockID was found\n";
    if (CellsByPart.count(key) == 0)
    {
      CellsByPart[key] = std::vector<int>();
    }
    CellsByPart[key].push_back(i);
    partNumbers.push_back(key);
  }

  // remove the duplicates from the partNumbers
  partNumbers.sort();
  partNumbers.unique();

  // write out each part
  for (iter = partNumbers.begin(); iter != partNumbers.end(); ++iter)
  {
    unsigned int j;
    std::list<int>::iterator iter2;
    int part = *iter;

    // write the part Header
    if (this->ShouldWriteGeometry())
    {
      blockCount += 1;
      this->WriteStringToFile("part", fd);
      this->WriteIntToFile(part, fd);
      // cout << "part is " << part << endl;
      this->WriteStringToFile("VTK Part", fd);
      this->WriteStringToFile("coordinates", fd);
    }

    // write the part header for data files
    for (j = 0; j < pointArrayFiles.size(); j++)
    {
      this->WriteStringToFile("part", pointArrayFiles[j]);
      this->WriteIntToFile(part, pointArrayFiles[j]);
      this->WriteStringToFile("coordinates", pointArrayFiles[j]);
    }
    for (j = 0; j < cellArrayFiles.size(); j++)
    {
      this->WriteStringToFile("part", cellArrayFiles[j]);
      this->WriteIntToFile(part, cellArrayFiles[j]);
    }

    // list of VTK Node Indices per part
    std::list<int> NodesPerPart;

    // map that goes from NodeID to the order, used for element connectivity
    std::map<int, int> NodeIdToOrder;

    // get a list of all the nodes used for a particular part
    for (j = 0; j < CellsByPart[part].size(); j++)
    {
      vtkCell* cell = input->GetCell(CellsByPart[part][j]);
      vtkIdList* points = cell->GetPointIds();
      for (int k = 0; k < points->GetNumberOfIds(); k++)
      {
        NodesPerPart.push_back(points->GetId(k));
      }
    }

    // remove the duplicate Node ID's
    NodesPerPart.sort();
    NodesPerPart.unique();

    // write the number of nodes
    if (this->ShouldWriteGeometry())
    {
      this->WriteIntToFile(static_cast<int>(NodesPerPart.size()), fd);

      // write the Node ID's to the file
      // also set up the NodeID->order map
      int NodeCount = 0;
      for (iter2 = NodesPerPart.begin(); iter2 != NodesPerPart.end(); ++iter2)
      {
        this->WriteIntToFile(*iter2, fd);
        NodeIdToOrder[*iter2] = NodeCount + 1;
        NodeCount++;
      }

      // EnSight format requires all the X's, then all the Y's, then all the Z's
      // write the X Coordinates

      vtkPoints* inputPoints = input->GetPoints();
      for (iter2 = NodesPerPart.begin(); iter2 != NodesPerPart.end(); ++iter2)
      {
        this->WriteFloatToFile((float)(inputPoints->GetPoint(*iter2)[0]), fd);
      }
      for (iter2 = NodesPerPart.begin(); iter2 != NodesPerPart.end(); ++iter2)
      {
        this->WriteFloatToFile((float)(inputPoints->GetPoint(*iter2)[1]), fd);
      }
      for (iter2 = NodesPerPart.begin(); iter2 != NodesPerPart.end(); ++iter2)
      {
        this->WriteFloatToFile((float)(inputPoints->GetPoint(*iter2)[2]), fd);
      }
    }

    // write the Node Data for this part
    for (j = 0; j < pointArrayFiles.size(); j++)
    {
      vtkDataArray* DataArray = input->GetPointData()->GetArray(j);
      // figure out what type of data it is
      int DataSize = DataArray->GetNumberOfComponents();

      for (int CurrentDimension = 0; CurrentDimension < DataSize; CurrentDimension++)
      {
        for (std::list<int>::iterator k = NodesPerPart.begin(); k != NodesPerPart.end(); ++k)
        {
          this->WriteFloatToFile(
            (float)(DataArray->GetTuple(*k)[CurrentDimension]), pointArrayFiles[j]);
        }
      }
    }

    // now we need to sort the cell list by element type
    // map is indexed by cell type has a vector of cell ID's
    std::map<int, std::vector<int>> CellsByElement;
    for (j = 0; j < CellsByPart[part].size(); j++)
    {
      int CellType = input->GetCell(CellsByPart[part][j])->GetCellType();
      int ghostLevel = 0;
      if (GhostData)
      {
        ghostLevel = (int)(GhostData->GetTuple(CellsByPart[part][j])[0]);
        if (ghostLevel & vtkDataSetAttributes::DUPLICATECELL)
        {
          ghostLevel = 1;
        }
      }
      // we want to sort out the ghost cells from the normal cells
      // so the element type will be ghostMultiplier*ghostLevel+elementType
      CellType += ghostLevel * GhostLevelMultiplier;
      if (CellsByElement.count(CellType) == 0)
      {
        CellsByElement[CellType] = std::vector<int>();
      }
      CellsByElement[CellType].push_back(CellsByPart[part][j]);
    }

    // now we need to go through each element type that EnSight understands
    std::vector<int> elementTypes;

    // list the types that EnSight understands
    // the noticeable absences are the ones without a fixed number of Nodes
    // for the ghost cell types
    elementTypes.push_back(VTK_VERTEX);
    elementTypes.push_back(VTK_LINE);
    elementTypes.push_back(VTK_TRIANGLE);
    elementTypes.push_back(VTK_QUAD);
    elementTypes.push_back(VTK_POLYGON);
    elementTypes.push_back(VTK_TETRA);
    elementTypes.push_back(VTK_HEXAHEDRON);
    elementTypes.push_back(VTK_WEDGE);
    elementTypes.push_back(VTK_PYRAMID);
    elementTypes.push_back(VTK_CONVEX_POINT_SET);
    elementTypes.push_back(VTK_QUADRATIC_EDGE);
    elementTypes.push_back(VTK_QUADRATIC_TRIANGLE);
    elementTypes.push_back(VTK_QUADRATIC_QUAD);
    elementTypes.push_back(VTK_QUADRATIC_TETRA);
    elementTypes.push_back(VTK_QUADRATIC_HEXAHEDRON);
    elementTypes.push_back(VTK_QUADRATIC_WEDGE);
    elementTypes.push_back(VTK_QUADRATIC_PYRAMID);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_VERTEX);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_LINE);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_TRIANGLE);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_QUAD);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_POLYGON);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_TETRA);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_HEXAHEDRON);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_WEDGE);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_PYRAMID);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_CONVEX_POINT_SET);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_QUADRATIC_EDGE);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_QUADRATIC_TRIANGLE);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_QUADRATIC_QUAD);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_QUADRATIC_TETRA);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_QUADRATIC_HEXAHEDRON);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_QUADRATIC_WEDGE);
    elementTypes.push_back(this->GhostLevelMultiplier + VTK_QUADRATIC_PYRAMID);

    // write out each type of element
    if (this->ShouldWriteGeometry())
    {
      for (j = 0; j < elementTypes.size(); j++)
      {
        unsigned int k;
        int elementType = elementTypes[j];
        if (CellsByElement.count(elementType) > 0)
        {
          // switch on element type to write correct type to file
          this->WriteElementTypeToFile(elementType, fd);

          // number of elements
          this->WriteIntToFile(static_cast<int>(CellsByElement[elementType].size()), fd);

          // element ID's
          for (k = 0; k < CellsByElement[elementType].size(); k++)
          {
            int CellId = CellsByElement[elementType][k];
            this->WriteIntToFile(CellId, fd);
          }

          // element conenctivity information
          for (k = 0; k < CellsByElement[elementType].size(); k++)
          {
            int CellId = CellsByElement[elementType][k];
            vtkIdList* PointIds = input->GetCell(CellId)->GetPointIds();
            for (int m = 0; m < PointIds->GetNumberOfIds(); m++)
            {
              int PointId = PointIds->GetId(m);
              this->WriteIntToFile(NodeIdToOrder[PointId], fd);
            }
          }
        }
      }
    }

    // write the Cell Data for this part
    for (j = 0; j < cellArrayFiles.size(); j++)
    {
      vtkDataArray* DataArray = input->GetCellData()->GetArray(j);
      // figure out what type of data it is
      int DataSize = DataArray->GetNumberOfComponents();

      for (unsigned int k = 0; k < elementTypes.size(); k++)
      {
        if (!CellsByElement[elementTypes[k]].empty())
        {
          this->WriteElementTypeToFile(elementTypes[k], cellArrayFiles[j]);
          for (unsigned int m = 0; m < CellsByElement[elementTypes[k]].size(); m++)
          {
            for (int CurrentDimension = 0; CurrentDimension < DataSize; CurrentDimension++)
            {
              this->WriteFloatToFile(
                (float)(DataArray->GetTuple(CellsByElement[elementTypes[k]][m])[CurrentDimension]),
                cellArrayFiles[j]);
            }
          }
        }
      }
    }
  }

  // now write the empty blocks
  // use the block list in the exodus model if it exists, otherwise
  // use the BlockID list if that exists.

  if (this->BlockIDs)
  {
    elementIDs = this->BlockIDs;
  }

  if (elementIDs)
  {
    // cout << "have " << this->NumberOfBlocks << " blocks " << endl;
    for (i = 0; i < this->NumberOfBlocks; i++)
    {
      unsigned int j;
      // figure out if the part was already written
      int part = elementIDs[i];
      if (std::find(partNumbers.begin(), partNumbers.end(), part) == partNumbers.end())
      {
        // no information about the part was written to the output files
        // so write some empty information
        if (this->ShouldWriteGeometry())
        {
          blockCount += 1;
          this->WriteStringToFile("part", fd);
          this->WriteIntToFile(part, fd);

          int exodusIndex = this->GetExodusModelIndex(elementIDs, this->NumberOfBlocks, part);

          if (exodusIndex != -1 && blockNames)
          {
            snprintf(charBuffer, sizeof(charBuffer), "Exodus-%s-%d", blockNames[exodusIndex], part);
            this->WriteStringToFile(charBuffer, fd);
          }
          else
          {
            this->WriteStringToFile("VTK Part", fd);
          }
        }

        // write the part header for data files
        for (j = 0; j < pointArrayFiles.size(); j++)
        {
          this->WriteStringToFile("part", pointArrayFiles[j]);
          this->WriteIntToFile(part, pointArrayFiles[j]);
        }
        for (j = 0; j < cellArrayFiles.size(); j++)
        {
          this->WriteStringToFile("part", cellArrayFiles[j]);
          this->WriteIntToFile(part, cellArrayFiles[j]);
        }
      }
    }
  }
  // cout << "wrote " << blockCount << "parts\n";
  if (this->TmpInput)
  {
    this->TmpInput->Delete();
    this->TmpInput = nullptr;
  }

  // close all the files
  if (fd)
  {
    fclose(fd);
  }

  for (ui = 0; ui < cellArrayFiles.size(); ui++)
  {
    fclose(cellArrayFiles[ui]);
  }
  for (ui = 0; ui < pointArrayFiles.size(); ui++)
  {
    fclose(pointArrayFiles[ui]);
  }
}

//------------------------------------------------------------------------------
void vtkEnSightWriter::WriteCaseFile(int TotalTimeSteps)
{

  vtkUnstructuredGrid* input = this->GetInput();
  int i;

  this->ComputeNames();

  if (!this->BaseName)
  {
    vtkErrorMacro("A FileName or Path/BaseName must be specified.");
    return;
  }

  char charBuffer[1024];
  snprintf(charBuffer, sizeof(charBuffer), "%s/%s.%d.case", this->Path, this->BaseName,
    this->ProcessNumber);

  // open the geometry file
  FILE* fd = nullptr;
  if (!(fd = OpenFile(charBuffer)))
  {
    return;
  }

  this->WriteTerminatedStringToFile("FORMAT\n", fd);
  this->WriteTerminatedStringToFile("type: ensight gold\n\n", fd);
  this->WriteTerminatedStringToFile("\nGEOMETRY\n", fd);

  // write the geometry file
  if (!this->TransientGeometry)
  {
    snprintf(charBuffer, sizeof(charBuffer), "model: %s.%d.00000.geo\n", this->BaseName,
      this->ProcessNumber);
    this->WriteTerminatedStringToFile(charBuffer, fd);
  }
  else
  {
    snprintf(charBuffer, sizeof(charBuffer), "model: 1 %s.%d.*****.geo\n", this->BaseName,
      this->ProcessNumber);
    this->WriteTerminatedStringToFile(charBuffer, fd);
  }

  this->WriteTerminatedStringToFile("\nVARIABLE\n", fd);

  char fileBuffer[256];

  // write the Node variable files
  for (i = 0; i < input->GetPointData()->GetNumberOfArrays(); i++)
  {

    strcpy(fileBuffer, input->GetPointData()->GetArray(i)->GetName());
    // skip arrays that were not written
    if (strcmp(fileBuffer, "GlobalElementId") == 0)
    {
      continue;
    }
    if (strcmp(fileBuffer, "GlobalNodeId") == 0)
    {
      continue;
    }
    if (strcmp(fileBuffer, "BlockId") == 0)
    {
      continue;
    }
    this->SanitizeFileName(fileBuffer);
    // figure out what kind of data it is
    char SmallBuffer[16];
    switch (input->GetPointData()->GetArray(i)->GetNumberOfComponents())
    {
      case (1):
        strcpy(SmallBuffer, "scalar");
        break;
      case (3):
        strcpy(SmallBuffer, "vector");
        break;
      case (6):
        strcpy(SmallBuffer, "tensor");
        break;
      case (9):
        strcpy(SmallBuffer, "tensor9");
        break;
    }
    if (TotalTimeSteps <= 1)
    {
      snprintf(charBuffer, sizeof(charBuffer), "%s per node: %s_n %s.%d.00000_n.%s\n", SmallBuffer,
        fileBuffer, this->BaseName, this->ProcessNumber, fileBuffer);
    }
    else
    {
      snprintf(charBuffer, sizeof(charBuffer), "%s per node: 1 %s_n %s.%d.*****_n.%s\n",
        SmallBuffer, fileBuffer, this->BaseName, this->ProcessNumber, fileBuffer);
    }
    this->WriteTerminatedStringToFile(charBuffer, fd);
  }

  // write the cell variable files
  for (i = 0; i < input->GetCellData()->GetNumberOfArrays(); i++)
  {
    // figure out what kind of data it is
    char SmallBuffer[16];

    strcpy(fileBuffer, input->GetCellData()->GetArray(i)->GetName());
    // skip arrays that were not written
    if (strcmp(fileBuffer, "GlobalElementId") == 0)
    {
      continue;
    }
    if (strcmp(fileBuffer, "GlobalNodeId") == 0)
    {
      continue;
    }
    if (strcmp(fileBuffer, "BlockId") == 0)
    {
      continue;
    }
    this->SanitizeFileName(fileBuffer);
    switch (input->GetCellData()->GetArray(i)->GetNumberOfComponents())
    {
      case (1):
        strcpy(SmallBuffer, "scalar");
        break;
      case (3):
        strcpy(SmallBuffer, "vector");
        break;
      case (6):
        strcpy(SmallBuffer, "tensor");
        break;
      case (9):
        strcpy(SmallBuffer, "tensor9");
        break;
    }
    if (TotalTimeSteps <= 1)
    {
      snprintf(charBuffer, sizeof(charBuffer), "%s per element: %s_c %s.%d.00000_c.%s\n",
        SmallBuffer, fileBuffer, this->BaseName, this->ProcessNumber, fileBuffer);
    }
    else
    {
      snprintf(charBuffer, sizeof(charBuffer), "%s per element: 1 %s_c %s.%d.*****_c.%s\n",
        SmallBuffer, fileBuffer, this->BaseName, this->ProcessNumber, fileBuffer);
    }
    this->WriteTerminatedStringToFile(charBuffer, fd);
  }

  // write time information if we have multiple timesteps
  if (TotalTimeSteps > 1)
  {
    this->WriteTerminatedStringToFile("\nTIME\n", fd);
    this->WriteTerminatedStringToFile("time set: 1\n", fd);
    snprintf(charBuffer, sizeof(charBuffer), "number of steps: %d\n", TotalTimeSteps);
    this->WriteTerminatedStringToFile(charBuffer, fd);
    this->WriteTerminatedStringToFile("filename start number: 00000\n", fd);
    this->WriteTerminatedStringToFile("filename increment: 00001\n", fd);
    this->WriteTerminatedStringToFile("time values: \n", fd);
    for (i = 0; i < TotalTimeSteps; i++)
    {
      double timestep = i;

      snprintf(charBuffer, sizeof(charBuffer), "%f ", timestep);
      this->WriteTerminatedStringToFile(charBuffer, fd);
      if (i % 6 == 0 && i > 0)
      {
        this->WriteTerminatedStringToFile("\n", fd);
      }
    }
  }
}

//------------------------------------------------------------------------------
void vtkEnSightWriter::WriteSOSCaseFile(int numProcs)
{
  this->ComputeNames();

  if (!this->BaseName)
  {
    vtkErrorMacro("A FileName or Path/BaseName must be specified.");
    return;
  }

  this->SanitizeFileName(this->BaseName);

  char charBuffer[512];
  snprintf(charBuffer, sizeof(charBuffer), "%s/%s.case.sos", this->Path, this->BaseName);

  FILE* fd = nullptr;
  if (!(fd = OpenFile(charBuffer)))
    return;

  this->WriteTerminatedStringToFile("FORMAT\n", fd);
  this->WriteTerminatedStringToFile("type: master_server gold\n\n", fd);

  this->WriteTerminatedStringToFile("SERVERS\n", fd);
  snprintf(charBuffer, sizeof(charBuffer), "number of servers: %d\n\n", numProcs);
  this->WriteTerminatedStringToFile(charBuffer, fd);

  // write the servers section with placeholders for the ensight server
  // location and server name
  int i = 0;
  for (i = 0; i < numProcs; i++)
  {
    snprintf(charBuffer, sizeof(charBuffer), "#Server %d\n", i);
    this->WriteTerminatedStringToFile(charBuffer, fd);
    this->WriteTerminatedStringToFile("#-------\n", fd);
    snprintf(charBuffer, sizeof(charBuffer), "machine id: MID%05d\n", i);
    this->WriteTerminatedStringToFile(charBuffer, fd);

    this->WriteTerminatedStringToFile("executable: MEX\n", fd);
    snprintf(charBuffer, sizeof(charBuffer), "data_path: %s\n", this->Path);
    this->WriteTerminatedStringToFile(charBuffer, fd);
    snprintf(charBuffer, sizeof(charBuffer), "casefile: %s.%d.case\n\n", this->BaseName, i);
    this->WriteTerminatedStringToFile(charBuffer, fd);
  }
}

//------------------------------------------------------------------------------
void vtkEnSightWriter::WriteStringToFile(const char* cstring, FILE* file)
{
  char cbuffer[81];
  unsigned long cstringLength = static_cast<unsigned long>(strlen(cstring));
  memcpy(cbuffer, cstring, vtkMath::Min(cstringLength, 80ul));
  for (int i = cstringLength; i < 81; ++i)
  {
    cbuffer[i] = '\0';
  }

  // Write a constant 80 bytes to the file.
  fwrite(cbuffer, sizeof(char), 80, file);
}

//------------------------------------------------------------------------------
void vtkEnSightWriter::WriteTerminatedStringToFile(const char* cstring, FILE* file)
{
  fwrite(cstring, sizeof(char), std::min(strlen(cstring), static_cast<size_t>(512)), file);
}

//------------------------------------------------------------------------------
void vtkEnSightWriter::WriteIntToFile(const int i, FILE* file)
{
  fwrite(&i, sizeof(int), 1, file);
}

//------------------------------------------------------------------------------
void vtkEnSightWriter::WriteFloatToFile(const float f, FILE* file)
{
  fwrite(&f, sizeof(float), 1, file);
}

//------------------------------------------------------------------------------
void vtkEnSightWriter::WriteElementTypeToFile(int elementType, FILE* fd)
{
  int ghostLevel = elementType / GhostLevelMultiplier;
  elementType = elementType % GhostLevelMultiplier;
  if (ghostLevel == 0)
  {
    switch (elementType)
    {
      case (VTK_VERTEX):
        this->WriteStringToFile("point", fd);
        break;
      case (VTK_LINE):
        this->WriteStringToFile("bar2", fd);
        break;
      case (VTK_TRIANGLE):
        this->WriteStringToFile("tria3", fd);
        break;
      case (VTK_QUAD):
        this->WriteStringToFile("quad4", fd);
        break;
      case (VTK_POLYGON):
        this->WriteStringToFile("nsided", fd);
        break;
      case (VTK_TETRA):
        this->WriteStringToFile("tetra4", fd);
        break;
      case (VTK_HEXAHEDRON):
        this->WriteStringToFile("hexa8", fd);
        break;
      case (VTK_WEDGE):
        this->WriteStringToFile("penta6", fd);
        break;
      case (VTK_PYRAMID):
        this->WriteStringToFile("pyramid5", fd);
        break;
      case (VTK_CONVEX_POINT_SET):
        this->WriteStringToFile("nfaced", fd);
        break;
      case (VTK_QUADRATIC_EDGE):
        this->WriteStringToFile("bar3", fd);
        break;
      case (VTK_QUADRATIC_TRIANGLE):
        this->WriteStringToFile("tria6", fd);
        break;
      case (VTK_QUADRATIC_QUAD):
        this->WriteStringToFile("quad8", fd);
        break;
      case (VTK_QUADRATIC_TETRA):
        this->WriteStringToFile("tetra10", fd);
        break;
      case (VTK_QUADRATIC_HEXAHEDRON):
        this->WriteStringToFile("hexa20", fd);
        break;
      case (VTK_QUADRATIC_WEDGE):
        this->WriteStringToFile("penta15", fd);
        break;
      case (VTK_QUADRATIC_PYRAMID):
        this->WriteStringToFile("pyramid13", fd);
        break;
    }
  }
  else
  {
    switch (elementType)
    {
      case (VTK_VERTEX):
        this->WriteStringToFile("g_point", fd);
        break;
      case (VTK_LINE):
        this->WriteStringToFile("g_bar2", fd);
        break;
      case (VTK_TRIANGLE):
        this->WriteStringToFile("g_tria3", fd);
        break;
      case (VTK_QUAD):
        this->WriteStringToFile("g_quad4", fd);
        break;
      case (VTK_POLYGON):
        this->WriteStringToFile("g_nsided", fd);
        break;
      case (VTK_TETRA):
        this->WriteStringToFile("g_tetra4", fd);
        break;
      case (VTK_HEXAHEDRON):
        this->WriteStringToFile("g_hexa8", fd);
        break;
      case (VTK_WEDGE):
        this->WriteStringToFile("g_penta6", fd);
        break;
      case (VTK_PYRAMID):
        this->WriteStringToFile("g_pyramid5", fd);
        break;
      case (VTK_CONVEX_POINT_SET):
        this->WriteStringToFile("g_nfaced", fd);
        break;
      case (VTK_QUADRATIC_EDGE):
        this->WriteStringToFile("g_bar3", fd);
        break;
      case (VTK_QUADRATIC_TRIANGLE):
        this->WriteStringToFile("g_tria6", fd);
        break;
      case (VTK_QUADRATIC_QUAD):
        this->WriteStringToFile("g_quad8", fd);
        break;
      case (VTK_QUADRATIC_TETRA):
        this->WriteStringToFile("g_tetra10", fd);
        break;
      case (VTK_QUADRATIC_HEXAHEDRON):
        this->WriteStringToFile("g_hexa20", fd);
        break;
      case (VTK_QUADRATIC_WEDGE):
        this->WriteStringToFile("g_penta15", fd);
        break;
      case (VTK_QUADRATIC_PYRAMID):
        this->WriteStringToFile("g_pyramid13", fd);
        break;
    }
  }
}

//------------------------------------------------------------------------------
bool vtkEnSightWriter::ShouldWriteGeometry()
{
  return (this->TransientGeometry || (this->TimeStep == 0));
}

//------------------------------------------------------------------------------
void vtkEnSightWriter::SanitizeFileName(char* name)
{

  char buffer[512];
  unsigned int i;
  int BufferPosition = 0;
  for (i = 0; i < strlen(name); i++)
  {
    if (name[i] != '/')
    {
      buffer[BufferPosition] = name[i];
      BufferPosition++;
    }
  }
  buffer[BufferPosition] = 0;
  for (i = 0; i < strlen(buffer); i++)
  {
    name[i] = buffer[i];
  }
  name[strlen(buffer)] = 0;
}

//------------------------------------------------------------------------------
FILE* vtkEnSightWriter::OpenFile(char* name)
{
  FILE* fd = vtksys::SystemTools::Fopen(name, "wb");

  if (fd == nullptr)
  {
    vtkErrorMacro("Error opening " << name << ": " << strerror(errno));
    return nullptr;
  }
  return fd;
}

//------------------------------------------------------------------------------
int vtkEnSightWriter::GetExodusModelIndex(int* elementArray, int numberElements, int partID)
{
  int i;
  for (i = 0; i < numberElements; i++)
  {
    if (elementArray[i] == partID)
      return i;
  }
  return -1;
}

//------------------------------------------------------------------------------
void vtkEnSightWriter::DefaultNames()
{
  char* path = new char[4];
  char* base = new char[20];
  strcpy(path, "./");
  strcpy(base, "EnSightWriter.out");

  this->SetPath(path);
  this->SetBaseName(base);
}

//------------------------------------------------------------------------------
void vtkEnSightWriter::ComputeNames()
{
  if (this->Path && this->BaseName)
  {
    return;
  }

  if (!this->FileName)
  {
    this->DefaultNames();
    return;
  }

  // FileName = Path/BaseName.digits.digits

  char* path = nullptr;
  char* base = nullptr;

  char* f = this->FileName;

  while (!isgraph(*f))
    f++; // find first printable character

  if (!*f)
  {
    // FileName is garbage
    DefaultNames();
    return;
  }

  char* buf = new char[strlen(f) + 1];
  strcpy(buf, f);

  char* slash = strrchr(buf, '/'); // final slash

  if (slash)
  {
    *slash = 0;
    path = new char[strlen(buf) + 1];
    strcpy(path, buf);
    f = slash + 1;
  }
  else
  {
    path = new char[4];
    strcpy(path, "./");

    f = buf;
  }

  char* firstChar = f;
  while (*f && (*f != '.'))
    f++;
  *f = 0;

  base = new char[strlen(firstChar) + 1];
  strcpy(base, firstChar);

  this->SetPath(path);
  this->SetBaseName(base);

  delete[] buf;
}
