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

 Program:   Visualization Toolkit
 Module:    vtkStructuredGridConnectivity.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.

 =========================================================================*/
#include "vtkStructuredGridConnectivity.h"

// VTK includes
#include "vtkIdList.h"
#include "vtkObjectFactory.h"
#include "vtkStructuredData.h"
#include "vtkStructuredExtent.h"
#include "vtkStructuredNeighbor.h"
#include "vtkUnsignedCharArray.h"

vtkStandardNewMacro(vtkStructuredGridConnectivity);

// Description:
// An enum to define the 6 block faces
namespace BlockFace
{
enum
{
  FRONT = 0,  // (+k diretion)
  BACK = 1,   // (-k direction)
  RIGHT = 2,  // (+i direction)
  LEFT = 3,   // (-i direction)
  TOP = 4,    // (+j direction)
  BOTTOM = 5, // (-j direction)
  NOT_ON_BLOCK_FACE = 6
};
}

//------------------------------------------------------------------------------
vtkStructuredGridConnectivity::vtkStructuredGridConnectivity()
{
  this->DataDimension = 0;
  this->DataDescription = -1;
  this->NumberOfGrids = 0;
  this->WholeExtent[0] = this->WholeExtent[1] = this->WholeExtent[2] = this->WholeExtent[3] =
    this->WholeExtent[4] = this->WholeExtent[5] = -1;
  this->GridExtents.clear();
}

//------------------------------------------------------------------------------
vtkStructuredGridConnectivity::~vtkStructuredGridConnectivity()
{
  this->GridExtents.clear();
  this->NeighborPair2NeighborListIndex.clear();
}

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

  os << "========================\n";
  os << "DATA DIMENSION: " << this->DataDimension << std::endl;
  os << "WHOLE EXTENT: [ ";
  for (int i = 0; i < 6; i++)
  {
    os << this->WholeExtent[i] << " ";
  }
  os << "]\n";
  os << "CONNECTIVITY INFORMATION: \n";
  for (unsigned int gridID = 0; gridID < this->NumberOfGrids; ++gridID)
  {
    int GridExtent[6];
    int RealExtent[6];
    this->GetGridExtent(gridID, GridExtent);
    this->GetRealExtent(gridID, GridExtent, RealExtent);
    os << "GRID[ " << gridID << "]: ";
    for (int i = 0; i < 6; i += 2)
    {
      os << " [";
      os << GridExtent[i] << ", " << GridExtent[i + 1] << "]";
    }
    os << " REAL EXTENT: ";
    for (int i = 0; i < 6; i += 2)
    {
      os << " [";
      os << RealExtent[i] << ", " << RealExtent[i + 1] << "]";
    }
    os << std::endl;
    os << " Connecting faces: " << this->GetNumberOfConnectingBlockFaces(gridID) << " ";

    os << "[ ";
    if (this->HasBlockConnection(gridID, BlockFace::FRONT))
    {
      os << "FRONT(+k) ";
    }
    if (this->HasBlockConnection(gridID, BlockFace::BACK))
    {
      os << "BACK(-k) ";
    }
    if (this->HasBlockConnection(gridID, BlockFace::RIGHT))
    {
      os << "RIGHT(+i) ";
    }
    if (this->HasBlockConnection(gridID, BlockFace::LEFT))
    {
      os << "LEFT(-i) ";
    }
    if (this->HasBlockConnection(gridID, BlockFace::TOP))
    {
      os << "TOP(+j) ";
    }
    if (this->HasBlockConnection(gridID, BlockFace::BOTTOM))
    {
      os << "BOTTOM(-j) ";
    }
    os << "] ";
    os << std::endl;

    for (unsigned int nei = 0; nei < this->Neighbors[gridID].size(); ++nei)
    {
      int NeiExtent[6];
      this->GetGridExtent(this->Neighbors[gridID][nei].NeighborID, NeiExtent);

      os << "\t N[" << nei << "] GRID ID:" << this->Neighbors[gridID][nei].NeighborID << " ";
      for (int i = 0; i < 6; i += 2)
      {
        os << " [";
        os << NeiExtent[i] << ", " << NeiExtent[i + 1] << "] ";
      }

      os << " overlaps @ ";
      for (int i = 0; i < 6; i += 2)
      {
        os << " [";
        os << this->Neighbors[gridID][nei].OverlapExtent[i] << ", ";
        os << this->Neighbors[gridID][nei].OverlapExtent[i + 1] << "] ";
      }

      os << " orientation: (";
      os << this->Neighbors[gridID][nei].Orientation[0] << ", ";
      os << this->Neighbors[gridID][nei].Orientation[1] << ", ";
      os << this->Neighbors[gridID][nei].Orientation[2] << ")\n ";
      os << std::endl;

      os << "\t RCVEXTENT: ";
      for (int i = 0; i < 6; i += 2)
      {
        os << " [";
        os << this->Neighbors[gridID][nei].RcvExtent[i] << ", ";
        os << this->Neighbors[gridID][nei].RcvExtent[i + 1] << "] ";
      }
      os << std::endl;

      os << "\t SNDEXTENT: ";
      for (int i = 0; i < 6; i += 2)
      {
        os << " [";
        os << this->Neighbors[gridID][nei].SendExtent[i] << ", ";
        os << this->Neighbors[gridID][nei].SendExtent[i + 1] << "] ";
      }
      os << std::endl << std::endl;

    } // END for all neis
  }   // END for all grids
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::RegisterGrid(const int gridID, int ext[6],
  vtkUnsignedCharArray* nodesGhostArray, vtkUnsignedCharArray* cellGhostArray,
  vtkPointData* pointData, vtkCellData* cellData, vtkPoints* gridNodes)
{
  assert("pre: gridID out-of-bounds!" &&
    (gridID >= 0 && gridID < static_cast<int>(this->NumberOfGrids)));

  for (int i = 0; i < 6; ++i)
  {
    this->GridExtents[gridID * 6 + i] = ext[i];
  }

  this->RegisterGridGhostArrays(gridID, nodesGhostArray, cellGhostArray);
  this->RegisterFieldData(gridID, pointData, cellData);
  this->RegisterGridNodes(gridID, gridNodes);
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::AcquireDataDescription()
{
  if (this->DataDescription != -1)
  {
    return;
  }

  int dims[3];

  vtkStructuredExtent::GetDimensions(this->WholeExtent, dims);

  this->DataDescription = vtkStructuredData::GetDataDescription(dims);
  this->DataDimension = vtkStructuredData::GetDataDimension(this->DataDescription);

  assert("pre: Error acquiring data description" && (this->DataDescription >= 0));
  assert("pre: grid description cannot be empty" && (this->DataDescription != VTK_EMPTY));
}

//------------------------------------------------------------------------------
vtkStructuredNeighbor vtkStructuredGridConnectivity::GetGridNeighbor(
  const int gridID, const int nei)
{
  assert("pre: gridID out-of-bounds!" &&
    (gridID >= 0 && gridID < static_cast<int>(this->NumberOfGrids)));
  assert(
    "pre: nei index is out-of-bounds!" && (nei >= 0) && (nei < this->GetNumberOfNeighbors(gridID)));

  return (this->Neighbors[gridID][nei]);
}

//------------------------------------------------------------------------------
vtkIdList* vtkStructuredGridConnectivity::GetNeighbors(const int gridID, int* extents)
{
  assert("pre: input extents array is nullptr" && (extents != nullptr));

  int N = this->GetNumberOfNeighbors(gridID);
  if (N < 1)
  {
    return nullptr;
  }

  vtkIdList* neiList = vtkIdList::New();
  neiList->SetNumberOfIds(N);

  unsigned int nei = 0;
  for (; nei < this->Neighbors[gridID].size(); ++nei)
  {
    vtkIdType neiId = this->Neighbors[gridID][nei].NeighborID;
    neiList->SetId(nei, neiId);
    for (int i = 0; i < 6; ++i)
    {
      extents[nei * 6 + i] = this->Neighbors[gridID][nei].OverlapExtent[i];
    }
  } // END for all neighbors

  assert("post: N==neiList.size()" && (N == neiList->GetNumberOfIds()));
  return (neiList);
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::ComputeNeighbors()
{
  // STEP 0: Acquire data description, i.e., determine how the structured data
  // is laid out, e.g., is it volumetric or 2-D along some plane, XY, XZ, or YZ.
  this->AcquireDataDescription();
  if (this->DataDescription == VTK_EMPTY || this->DataDescription == VTK_SINGLE_POINT)
  {
    return;
  }

  // STEP 1: Establish neighbors based on the structured extents.
  for (unsigned int i = 0; i < this->NumberOfGrids; ++i)
  {
    this->SetBlockTopology(i);
    for (unsigned int j = i + 1; j < this->NumberOfGrids; ++j)
    {
      this->EstablishNeighbors(i, j);
    } // END for all j
  }   // END for all i

  // STEP 2: Fill the ghost arrays
  for (unsigned int i = 0; i < this->NumberOfGrids; ++i)
  {
    // NOTE: typically remote grids have nullptr ghost arrays, by this approach
    // ComputeNeighbors() can be called transparently from
    // vtkPStructuredGridConnectivity without any modification.
    if (this->GridPointGhostArrays[i] != nullptr)
    {
      this->FillGhostArrays(i, this->GridPointGhostArrays[i], this->GridCellGhostArrays[i]);
    }
  } // END for all grids
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::SearchNeighbors(
  const int gridID, const int i, const int j, const int k, vtkIdList* neiList)
{
  assert("pre: neiList should not be nullptr" && (neiList != nullptr));
  assert("pre: gridID is out-of-bounds" &&
    ((gridID >= 0) && (gridID < static_cast<int>(this->NumberOfGrids))));

  for (unsigned int nei = 0; nei < this->Neighbors[gridID].size(); ++nei)
  {
    vtkStructuredNeighbor* myNei = &this->Neighbors[gridID][nei];
    assert("pre: myNei != nullptr" && (myNei != nullptr));

    if (this->IsNodeWithinExtent(i, j, k, myNei->OverlapExtent))
    {
      neiList->InsertNextId(myNei->NeighborID);
    }
  } // END for all neis
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::MarkCellProperty(
  unsigned char& pfield, unsigned char* nodeGhostFields, const int numNodes)
{
  // Sanity check
  assert("pre: node ghostfields should not be nullptr" && (nodeGhostFields != nullptr));

  pfield = 0;

  for (int i = 0; i < numNodes; ++i)
  {
    if (nodeGhostFields[i] & vtkDataSetAttributes::DUPLICATEPOINT)
    {
      pfield |= vtkDataSetAttributes::DUPLICATECELL;
      return;
    }
  } // END for all nodes
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::MarkNodeProperty(const int gridID, const int i, const int j,
  const int k, int ext[6], int realExtent[6], unsigned char& p)
{
  p = 0;

  // Check if the node is an interior a node, i.e., it is not on any boundary
  // shared or real boundary and not in a ghost region. Interior nodes can only
  // be internal nodes!
  if (!this->IsNodeInterior(i, j, k, realExtent))
  {
    // If the node is on the boundary of the computational domain mark it
    // if( this->IsNodeOnBoundary(i,j,k) )
    // {
    //   BOUNDARY might be used in the future
    // }

    // Check if the node is also on a shared boundary or if it is a ghost node
    if (this->IsNodeOnSharedBoundary(gridID, realExtent, i, j, k))
    {
      // SHARED might be used in the future

      // For shared nodes we must check for ownership
      vtkIdList* neiList = vtkIdList::New();
      this->SearchNeighbors(gridID, i, j, k, neiList);

      if (neiList->GetNumberOfIds() > 0)
      {
        int neiRealExtent[6];
        int neiGridExtent[6];

        for (vtkIdType nei = 0; nei < neiList->GetNumberOfIds(); ++nei)
        {
          vtkIdType neiIdx = neiList->GetId(nei);
          this->GetGridExtent(neiIdx, neiGridExtent);
          this->GetRealExtent(neiIdx, neiGridExtent, neiRealExtent);

          // If my gridID is not the smallest gridID that shares the point,then
          // I don't own the point.
          // The convention is that the grid with the smallest gridID will own the
          // point and all other grids should IGNORE it when computing statistics
          // etc.
          if (this->IsNodeWithinExtent(i, j, k, neiRealExtent) && gridID > neiList->GetId(nei))
          {
            p |= vtkDataSetAttributes::HIDDENPOINT;
            break;
          }
        } // END for all neis
      }   // END if neisList isn't empty
      neiList->Delete();
    } // END if node is on a shared boundary
    else if (this->IsGhostNode(ext, realExtent, i, j, k))
    {
      p |= vtkDataSetAttributes::DUPLICATEPOINT;
    }
  }
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::FillNodesGhostArray(const int gridID, const int dataDescription,
  int GridExtent[6], int RealExtent[6], vtkUnsignedCharArray* nodesArray)
{
  int ijk[3];
  for (int i = GridExtent[0]; i <= GridExtent[1]; ++i)
  {
    for (int j = GridExtent[2]; j <= GridExtent[3]; ++j)
    {
      for (int k = GridExtent[4]; k <= GridExtent[5]; ++k)
      {
        ijk[0] = i;
        ijk[1] = j;
        ijk[2] = k;
        vtkIdType idx =
          vtkStructuredData::ComputePointIdForExtent(GridExtent, ijk, dataDescription);

        this->MarkNodeProperty(
          gridID, i, j, k, GridExtent, RealExtent, *nodesArray->GetPointer(idx));
      } // END for all k
    }   // END for all j
  }     // END for all i
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::FillCellsGhostArray(const int dataDescription,
  const int numNodesPerCell, int dims[3], int CellExtent[6], vtkUnsignedCharArray* nodesArray,
  vtkUnsignedCharArray* cellsArray)
{
  assert("pre: nodes array should not be nullptr" && (nodesArray != nullptr));

  if (cellsArray == nullptr)
  {
    return;
  }

  vtkIdList* cellNodeIds = vtkIdList::New();
  unsigned char* cellNodeGhostFields = new unsigned char[numNodesPerCell];

  int ijk[3];
  for (int i = CellExtent[0]; i <= CellExtent[1]; ++i)
  {
    for (int j = CellExtent[2]; j <= CellExtent[3]; ++j)
    {
      for (int k = CellExtent[4]; k <= CellExtent[5]; ++k)
      {
        ijk[0] = i;
        ijk[1] = j;
        ijk[2] = k;

        // Note: this is really a cell index, since it is computed from the
        // cell extent
        vtkIdType idx =
          vtkStructuredData::ComputePointIdForExtent(CellExtent, ijk, dataDescription);

        cellNodeIds->Reset();
        vtkStructuredData::GetCellPoints(idx, cellNodeIds, dataDescription, dims);
        assert(cellNodeIds->GetNumberOfIds() == numNodesPerCell);
        assert(cellNodeGhostFields != nullptr);

        for (int ii = 0; ii < numNodesPerCell; ++ii)
        {
          vtkIdType nodeIdx = cellNodeIds->GetId(ii);
          cellNodeGhostFields[ii] = *nodesArray->GetPointer(nodeIdx);
        } // END for all nodes

        this->MarkCellProperty(*cellsArray->GetPointer(idx), cellNodeGhostFields, numNodesPerCell);
      } // END for all cells along k
    }   // END for all cells along j
  }     // END for all cells along i

  delete[] cellNodeGhostFields;
  cellNodeIds->Delete();
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::FillGhostArrays(
  const int gridID, vtkUnsignedCharArray* nodesArray, vtkUnsignedCharArray* cellsArray)
{
  if (nodesArray == nullptr)
  {
    return;
  }

  // STEP 0: Get the grid information
  int GridExtent[6];
  this->GetGridExtent(gridID, GridExtent);

  // STEP 1: Real extent
  int RealExtent[6];
  this->GetRealExtent(gridID, GridExtent, RealExtent);

  // STEP 2: Get the data description
  int dataDescription = vtkStructuredData::GetDataDescriptionFromExtent(GridExtent);

  // STEP 3: Get the cell extent
  int CellExtent[6];
  vtkStructuredData::GetCellExtentFromPointExtent(GridExtent, CellExtent, dataDescription);

  // STEP 4: Get the data dimension
  int dim = vtkStructuredData::GetDataDimension(dataDescription);
  assert("pre: data dimensions must be 1, 2 or 3" && (dim >= 1) && (dim <= 3));

  // STEP 5: Get the grid dimensions from the given extent.
  int dims[3];
  vtkStructuredData::GetDimensionsFromExtent(GridExtent, dims);

  // STEP 6: Get the number of nodes per cell
  int numNodes = this->GetNumberOfNodesPerCell(dim);

  // STEP 7: Mark nodes
  this->FillNodesGhostArray(gridID, dataDescription, GridExtent, RealExtent, nodesArray);

  // STEP 8: Mark Cells
  this->FillCellsGhostArray(dataDescription, numNodes, dims, CellExtent, nodesArray, cellsArray);
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::GetRealExtent(
  const int gridID, int GridExtent[6], int RealExtent[6])
{
  for (int i = 0; i < 6; ++i)
  {
    RealExtent[i] = GridExtent[i];
  }

  if (this->NumberOfGhostLayers == 0)
  {
    return;
  }

  switch (this->DataDescription)
  {
    case VTK_X_LINE:
      if (this->HasBlockConnection(gridID, BlockFace::LEFT))
      {
        RealExtent[0] += this->NumberOfGhostLayers; // imin
      }
      if (this->HasBlockConnection(gridID, BlockFace::RIGHT))
      {
        RealExtent[1] -= this->NumberOfGhostLayers; // imax
      }
      break;
    case VTK_Y_LINE:
      if (this->HasBlockConnection(gridID, BlockFace::BOTTOM))
      {
        RealExtent[2] += this->NumberOfGhostLayers; // jmin
      }
      if (this->HasBlockConnection(gridID, BlockFace::TOP))
      {
        RealExtent[3] -= this->NumberOfGhostLayers; // jmax
      }
      break;
    case VTK_Z_LINE:
      if (this->HasBlockConnection(gridID, BlockFace::BACK))
      {
        RealExtent[4] += this->NumberOfGhostLayers; // kmin
      }
      if (this->HasBlockConnection(gridID, BlockFace::FRONT))
      {
        RealExtent[5] -= this->NumberOfGhostLayers; // kmax
      }
      break;
    case VTK_XY_PLANE:
      if (this->HasBlockConnection(gridID, BlockFace::LEFT))
      {
        RealExtent[0] += this->NumberOfGhostLayers; // imin
      }
      if (this->HasBlockConnection(gridID, BlockFace::RIGHT))
      {
        RealExtent[1] -= this->NumberOfGhostLayers; // imax
      }
      if (this->HasBlockConnection(gridID, BlockFace::BOTTOM))
      {
        RealExtent[2] += this->NumberOfGhostLayers; // jmin
      }
      if (this->HasBlockConnection(gridID, BlockFace::TOP))
      {
        RealExtent[3] -= this->NumberOfGhostLayers; // jmax
      }
      break;
    case VTK_YZ_PLANE:
      if (this->HasBlockConnection(gridID, BlockFace::BOTTOM))
      {
        RealExtent[2] += this->NumberOfGhostLayers; // jmin
      }
      if (this->HasBlockConnection(gridID, BlockFace::TOP))
      {
        RealExtent[3] -= this->NumberOfGhostLayers; // jmax
      }
      if (this->HasBlockConnection(gridID, BlockFace::BACK))
      {
        RealExtent[4] += this->NumberOfGhostLayers; // kmin
      }
      if (this->HasBlockConnection(gridID, BlockFace::FRONT))
      {
        RealExtent[5] -= this->NumberOfGhostLayers; // kmax
      }
      break;
    case VTK_XZ_PLANE:
      if (this->HasBlockConnection(gridID, BlockFace::LEFT))
      {
        RealExtent[0] += this->NumberOfGhostLayers; // imin
      }
      if (this->HasBlockConnection(gridID, BlockFace::RIGHT))
      {
        RealExtent[1] -= this->NumberOfGhostLayers; // imax
      }
      if (this->HasBlockConnection(gridID, BlockFace::BACK))
      {
        RealExtent[4] += this->NumberOfGhostLayers; // kmin
      }
      if (this->HasBlockConnection(gridID, BlockFace::FRONT))
      {
        RealExtent[5] -= this->NumberOfGhostLayers; // kmax
      }
      break;
    case VTK_XYZ_GRID:
      if (this->HasBlockConnection(gridID, BlockFace::LEFT))
      {
        RealExtent[0] += this->NumberOfGhostLayers; // imin
      }
      if (this->HasBlockConnection(gridID, BlockFace::RIGHT))
      {
        RealExtent[1] -= this->NumberOfGhostLayers; // imax
      }
      if (this->HasBlockConnection(gridID, BlockFace::BOTTOM))
      {
        RealExtent[2] += this->NumberOfGhostLayers; // jmin
      }
      if (this->HasBlockConnection(gridID, BlockFace::TOP))
      {
        RealExtent[3] -= this->NumberOfGhostLayers; // jmax
      }
      if (this->HasBlockConnection(gridID, BlockFace::BACK))
      {
        RealExtent[4] += this->NumberOfGhostLayers; // kmin
      }
      if (this->HasBlockConnection(gridID, BlockFace::FRONT))
      {
        RealExtent[5] -= this->NumberOfGhostLayers; // kmax
      }
      break;
    default:
      std::cout << "Data description is: " << this->DataDescription << "\n";
      std::cout.flush();
      assert("pre: Undefined data-description!" && false);
  }
  vtkStructuredExtent::Clamp(RealExtent, this->WholeExtent);
}

//------------------------------------------------------------------------------
bool vtkStructuredGridConnectivity::IsNodeOnSharedBoundary(
  const int gridID, int RealExtent[6], const int i, const int j, const int k)
{
  if (this->IsNodeOnBoundaryOfExtent(i, j, k, RealExtent))
  {
    int orient[3];
    this->GetIJKBlockOrientation(i, j, k, RealExtent, orient);
    for (int ii = 0; ii < 3; ++ii)
    {
      if ((orient[ii] != BlockFace::NOT_ON_BLOCK_FACE) &&
        this->HasBlockConnection(gridID, orient[ii]))
      {
        return true;
      }
    } // END for all dimensions
    return false;
  }
  else
  {
    return false;
  }
}

//------------------------------------------------------------------------------
bool vtkStructuredGridConnectivity::IsGhostNode(
  int GridExtent[6], int RealExtent[6], const int i, const int j, const int k)
{
  // STEP 0: Check if there are any ghost-layers. Note, if the original data
  // that the user is registering contains ghost-layers, the users must set
  // the number of ghost-layers.
  if (this->NumberOfGhostLayers == 0)
  {
    // Grid has no ghost-layers, so, the node cannot be a ghost node
    return false;
  }

  bool status = false;
  if (!this->IsNodeWithinExtent(i, j, k, RealExtent) &&
    this->IsNodeWithinExtent(i, j, k, GridExtent))
  {
    status = true;
  }
  return (status);
}

//------------------------------------------------------------------------------
bool vtkStructuredGridConnectivity::IsNodeOnBoundary(const int i, const int j, const int k)
{
  return (this->IsNodeOnBoundaryOfExtent(i, j, k, this->WholeExtent));
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::EstablishNeighbors(const int i, const int j)
{
  assert("pre: i < j" && (i < j));

  int iGridExtent[6];
  int jGridExtent[6];
  this->GetGridExtent(i, iGridExtent);
  this->GetGridExtent(j, jGridExtent);

  // A 3-tuple that defines the grid orientation of the form {i,j,k} where
  // i=0;, j=1, k=2. For example, let's say that we want to define the
  // orientation to be in the XZ plane, then, orientation array would be
  // constructed as follows: {0,2 -1}, where -1 indicates a NIL value.
  int orientation[3];

  // A place holder for setting up ndim, which store the dimensionality of
  // the data.
  int ndim = 3;

  switch (this->DataDescription)
  {
    case VTK_X_LINE:
      ndim = 1;
      orientation[0] = 0;
      orientation[1] = -1;
      orientation[2] = -1;
      break;
    case VTK_Y_LINE:
      ndim = 1;
      orientation[0] = 1;
      orientation[1] = -1;
      orientation[2] = -1;
      break;
    case VTK_Z_LINE:
      ndim = 1;
      orientation[0] = 2;
      orientation[1] = -1;
      orientation[2] = -1;
      break;
    case VTK_XY_PLANE:
      ndim = 2;
      orientation[0] = 0;
      orientation[1] = 1;
      orientation[2] = -1;
      break;
    case VTK_YZ_PLANE:
      ndim = 2;
      orientation[0] = 1;
      orientation[1] = 2;
      orientation[2] = -1;
      break;
    case VTK_XZ_PLANE:
      ndim = 2;
      orientation[0] = 0;
      orientation[1] = 2;
      orientation[2] = -1;
      break;
    case VTK_XYZ_GRID:
      ndim = 3;
      orientation[0] = 0;
      orientation[1] = 1;
      orientation[2] = 2;
      break;
    default:
      std::cout << "Data description is: " << this->DataDescription << "\n";
      std::cout.flush();
      assert("pre: Undefined data-description!" && false);
  } // END switch

  this->DetectNeighbors(i, j, iGridExtent, jGridExtent, orientation, ndim);
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::DetectNeighbors(
  const int i, const int j, int ex1[6], int ex2[6], int orientation[3], int ndim)
{
  std::vector<int> status;
  status.resize(ndim);

  int A[2];
  int B[2];
  int overlap[2];
  int iOrientation[3];
  int jOrientation[3];

  int overlapExtent[6];
  for (int ii = 0; ii < 3; ++ii)
  {
    overlapExtent[ii * 2] = overlapExtent[ii * 2 + 1] = 0;
    iOrientation[ii] = vtkStructuredNeighbor::UNDEFINED;
    jOrientation[ii] = vtkStructuredNeighbor::UNDEFINED;
  }

  int dim = 0;
  int idx = 0;
  for (dim = 0; dim < ndim; ++dim)
  {
    idx = orientation[dim];
    A[0] = ex1[idx * 2];
    A[1] = ex1[idx * 2 + 1];
    B[0] = ex2[idx * 2];
    B[1] = ex2[idx * 2 + 1];
    status[idx] = this->IntervalOverlap(A, B, overlap);
    if (status[idx] == VTK_NO_OVERLAP)
    {
      return; /* No neighbors */
    }

    overlapExtent[idx * 2] = overlap[0];
    overlapExtent[idx * 2 + 1] = overlap[1];

    this->DetermineNeighborOrientation(idx, A, B, overlap, iOrientation);
    this->DetermineNeighborOrientation(idx, B, A, overlap, jOrientation);
  } // END for all dimensions

  this->SetNeighbors(i, j, iOrientation, jOrientation, overlapExtent);
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::SetBlockTopology(const int gridID)
{
  int gridExtent[6];
  this->GetGridExtent(gridID, gridExtent);

  // Check in IMIN
  if (gridExtent[0] > this->WholeExtent[0])
  {
    this->AddBlockConnection(gridID, BlockFace::LEFT);
  }

  // Check in IMAX
  if (gridExtent[1] < this->WholeExtent[1])
  {
    this->AddBlockConnection(gridID, BlockFace::RIGHT);
  }

  // Check in JMIN
  if (gridExtent[2] > this->WholeExtent[2])
  {
    this->AddBlockConnection(gridID, BlockFace::BOTTOM);
  }

  // Check in JMAX
  if (gridExtent[3] < this->WholeExtent[3])
  {
    this->AddBlockConnection(gridID, BlockFace::TOP);
  }

  // Check in KMIN
  if (gridExtent[4] > this->WholeExtent[4])
  {
    this->AddBlockConnection(gridID, BlockFace::BACK);
  }

  // Check in KMAX
  if (gridExtent[5] < this->WholeExtent[5])
  {
    this->AddBlockConnection(gridID, BlockFace::FRONT);
  }
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::SetNeighbors(
  const int i, const int j, int i2jOrientation[3], int j2iOrientation[3], int overlapExtent[6])
{
  vtkStructuredNeighbor Ni2j(j, overlapExtent, i2jOrientation);
  vtkStructuredNeighbor Nj2i(i, overlapExtent, j2iOrientation);

  // STEP 0: Setup i-to-j
  this->Neighbors[i].push_back(Ni2j);
  int i2jNeiIdx = static_cast<int>(this->Neighbors[i].size()) - 1;
  std::pair<int, int> i2jPair = std::make_pair(i, j);
  assert("ERROR: Duplicate neighboring pair!" &&
    this->NeighborPair2NeighborListIndex.find(i2jPair) ==
      this->NeighborPair2NeighborListIndex.end());
  this->NeighborPair2NeighborListIndex[i2jPair] = i2jNeiIdx;

  // STEP 1: Setup j-to-i
  this->Neighbors[j].push_back(Nj2i);
  int j2iNeiIdx = static_cast<int>(this->Neighbors[j].size()) - 1;
  std::pair<int, int> j2iPair = std::make_pair(j, i);
  assert("ERROR: Duplicate neighboring pair!" &&
    this->NeighborPair2NeighborListIndex.find(j2iPair) ==
      this->NeighborPair2NeighborListIndex.end());
  this->NeighborPair2NeighborListIndex[j2iPair] = j2iNeiIdx;
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::PrintExtent(int ex[6])
{
  for (int i = 0; i < 3; ++i)
  {
    std::cout << " [" << ex[i * 2] << ", " << ex[i * 2 + 1] << "] ";
  }
  std::cout << std::endl;
  std::cout.flush();
}

//------------------------------------------------------------------------------
int vtkStructuredGridConnectivity::DoPartialOverlap(int s[2], int S[2], int overlap[2])
{
  if (this->InBounds(s[0], S[0], S[1]) && this->InBounds(s[1], S[0], S[1]))
  {
    overlap[0] = s[0];
    overlap[1] = s[1];
    return VTK_PARTIAL_OVERLAP;
  }
  else if (this->InBounds(s[0], S[0], S[1]))
  {
    overlap[0] = s[0];
    overlap[1] = S[1];
    if (overlap[0] == overlap[1])
    {
      return VTK_NODE_OVERLAP;
    }
    else
    {
      return VTK_PARTIAL_OVERLAP;
    }
  }
  else if (this->InBounds(s[1], S[0], S[1]))
  {
    overlap[0] = S[0];
    overlap[1] = s[1];
    if (overlap[0] == overlap[1])
    {
      return VTK_NODE_OVERLAP;
    }
    else
    {
      return VTK_PARTIAL_OVERLAP;
    }
  }
  return VTK_NO_OVERLAP;
}

//------------------------------------------------------------------------------
int vtkStructuredGridConnectivity::PartialOverlap(
  int A[2], const int CardinalityOfA, int B[2], const int CardinalityOfB, int overlap[2])
{
  if (CardinalityOfA > CardinalityOfB)
  {
    return (this->DoPartialOverlap(B, A, overlap));
  }
  else if (CardinalityOfB > CardinalityOfA)
  {
    return (this->DoPartialOverlap(A, B, overlap));
  }
  else
  {
    return (this->DoPartialOverlap(A, B, overlap));
  }

  // Code should not reach here!
  //  assert( "Hmm...code should not reach here!" && false );
  //  return VTK_NO_OVERLAP;
}

//------------------------------------------------------------------------------
int vtkStructuredGridConnectivity::IntervalOverlap(int A[2], int B[2], int overlap[2])
{
  // STEP 0: Check if we must check for a partial overlap
  int CardinalityOfA = this->Cardinality(A);
  int CardinalityOfB = this->Cardinality(B);
  return (this->PartialOverlap(A, CardinalityOfA, B, CardinalityOfB, overlap));
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::GetIJKBlockOrientation(
  const int i, const int j, const int k, int ext[6], int orientation[3])
{
  orientation[0] = orientation[1] = orientation[2] = BlockFace::NOT_ON_BLOCK_FACE;

  switch (this->DataDescription)
  {
    case VTK_X_LINE:
      orientation[0] = this->Get1DOrientation(
        i, ext[0], ext[1], BlockFace::LEFT, BlockFace::RIGHT, BlockFace::NOT_ON_BLOCK_FACE);
      break;
    case VTK_Y_LINE:
      orientation[1] = this->Get1DOrientation(
        j, ext[2], ext[3], BlockFace::BOTTOM, BlockFace::TOP, BlockFace::NOT_ON_BLOCK_FACE);
      break;
    case VTK_Z_LINE:
      orientation[2] = this->Get1DOrientation(
        k, ext[4], ext[5], BlockFace::BACK, BlockFace::FRONT, BlockFace::NOT_ON_BLOCK_FACE);
      break;
    case VTK_XY_PLANE:
      orientation[0] = this->Get1DOrientation(
        i, ext[0], ext[1], BlockFace::LEFT, BlockFace::RIGHT, BlockFace::NOT_ON_BLOCK_FACE);
      orientation[1] = this->Get1DOrientation(
        j, ext[2], ext[3], BlockFace::BOTTOM, BlockFace::TOP, BlockFace::NOT_ON_BLOCK_FACE);
      break;
    case VTK_YZ_PLANE:
      orientation[1] = this->Get1DOrientation(
        j, ext[2], ext[3], BlockFace::BOTTOM, BlockFace::TOP, BlockFace::NOT_ON_BLOCK_FACE);
      orientation[2] = this->Get1DOrientation(
        k, ext[4], ext[5], BlockFace::BACK, BlockFace::FRONT, BlockFace::NOT_ON_BLOCK_FACE);
      break;
    case VTK_XZ_PLANE:
      orientation[0] = this->Get1DOrientation(
        i, ext[0], ext[1], BlockFace::LEFT, BlockFace::RIGHT, BlockFace::NOT_ON_BLOCK_FACE);
      orientation[2] = this->Get1DOrientation(
        k, ext[4], ext[5], BlockFace::BACK, BlockFace::FRONT, BlockFace::NOT_ON_BLOCK_FACE);
      break;
    case VTK_XYZ_GRID:
      orientation[0] = this->Get1DOrientation(
        i, ext[0], ext[1], BlockFace::LEFT, BlockFace::RIGHT, BlockFace::NOT_ON_BLOCK_FACE);
      orientation[1] = this->Get1DOrientation(
        j, ext[2], ext[3], BlockFace::BOTTOM, BlockFace::TOP, BlockFace::NOT_ON_BLOCK_FACE);
      orientation[2] = this->Get1DOrientation(
        k, ext[4], ext[5], BlockFace::BACK, BlockFace::FRONT, BlockFace::NOT_ON_BLOCK_FACE);
      break;
    default:
      std::cout << "Data description is: " << this->DataDescription << "\n";
      std::cout.flush();
      assert("pre: Undefined data-description!" && false);
  }
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::CreateGhostedExtent(const int gridID, const int N)
{
  assert("pre: gridID is out-of-bounds!" && (gridID >= 0) &&
    (gridID < static_cast<int>(this->NumberOfGrids)));
  assert("pre: ghosted-extents vector has not been allocated" &&
    (this->NumberOfGrids == this->GhostedExtents.size() / 6));
  assert(
    "pre: Number of ghost-layers requested should not be 0" && (this->NumberOfGhostLayers > 0));

  int ext[6];
  this->GetGridExtent(gridID, ext);
  this->SetGhostedGridExtent(gridID, ext);

  int* ghostedExtent = &this->GhostedExtents[gridID * 6];

  switch (this->DataDescription)
  {
    case VTK_X_LINE:
      this->GetGhostedExtent(ghostedExtent, ext, 0, 1, N);
      break;
    case VTK_Y_LINE:
      this->GetGhostedExtent(ghostedExtent, ext, 2, 3, N);
      break;
    case VTK_Z_LINE:
      this->GetGhostedExtent(ghostedExtent, ext, 4, 5, N);
      break;
    case VTK_XY_PLANE:
      this->GetGhostedExtent(ghostedExtent, ext, 0, 1, N);
      this->GetGhostedExtent(ghostedExtent, ext, 2, 3, N);
      break;
    case VTK_YZ_PLANE:
      this->GetGhostedExtent(ghostedExtent, ext, 2, 3, N);
      this->GetGhostedExtent(ghostedExtent, ext, 4, 5, N);
      break;
    case VTK_XZ_PLANE:
      this->GetGhostedExtent(ghostedExtent, ext, 0, 1, N);
      this->GetGhostedExtent(ghostedExtent, ext, 4, 5, N);
      break;
    case VTK_XYZ_GRID:
      this->GetGhostedExtent(ghostedExtent, ext, 0, 1, N);
      this->GetGhostedExtent(ghostedExtent, ext, 2, 3, N);
      this->GetGhostedExtent(ghostedExtent, ext, 4, 5, N);
      break;
    default:
      std::cout << "Data description is: " << this->DataDescription << "\n";
      std::cout.flush();
      assert("pre: Undefined data-description!" && false);
  } // END switch
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::CreateGhostedMaskArrays(const int gridID)
{
  // Sanity check
  assert("pre: gridID is out-of-bounds!" && (gridID >= 0) &&
    (gridID < static_cast<int>(this->NumberOfGrids)));
  assert("pre: GhostedPointGhostArray has not been allocated" &&
    (this->NumberOfGrids == this->GhostedPointGhostArray.size()));
  assert("pre: GhostedCellGhostArray has not been allocated" &&
    (this->NumberOfGrids == this->GhostedCellGhostArray.size()));

  // STEP 0: Initialize the ghosted node and cell arrays
  if (this->GhostedPointGhostArray[gridID] == nullptr)
  {
    this->GhostedPointGhostArray[gridID] = vtkUnsignedCharArray::New();
  }
  else
  {
    this->GhostedPointGhostArray[gridID]->Reset();
  }

  if (this->GhostedCellGhostArray[gridID] == nullptr)
  {
    this->GhostedCellGhostArray[gridID] = vtkUnsignedCharArray::New();
  }
  else
  {
    this->GhostedCellGhostArray[gridID]->Reset();
  }

  // STEP 1: Get the ghosted extent
  int ghostedExtent[6];
  this->GetGhostedGridExtent(gridID, ghostedExtent);

  // STEP 2: Get the grid extent
  int gridExtent[6];
  this->GetGridExtent(gridID, gridExtent);

  int numNodes = vtkStructuredData::GetNumberOfPoints(ghostedExtent, this->DataDescription);

  int numCells = vtkStructuredData::GetNumberOfCells(ghostedExtent, this->DataDescription);

  // STEP 3: Allocated the ghosted node and cell arrays and initialize them
  this->GhostedPointGhostArray[gridID]->Allocate(numNodes);
  this->GhostedCellGhostArray[gridID]->Allocate(numCells);

  // Initialize the arrays
  unsigned char* pnodes = this->GhostedPointGhostArray[gridID]->WritePointer(0, numNodes);
  memset(pnodes, 0, numNodes);
  unsigned char* pcells = this->GhostedCellGhostArray[gridID]->WritePointer(0, numCells);
  memset(pcells, 0, numCells);

  // STEP 4: Loop through the ghosted extent and mark the nodes in the ghosted
  // extent accordingly. If the node exists in the grown extent
  int ijk[3];
  unsigned char p = 0;
  for (int i = ghostedExtent[0]; i <= ghostedExtent[1]; ++i)
  {
    for (int j = ghostedExtent[2]; j <= ghostedExtent[3]; ++j)
    {
      for (int k = ghostedExtent[4]; k <= ghostedExtent[5]; ++k)
      {
        ijk[0] = i;
        ijk[1] = j;
        ijk[2] = k;

        vtkIdType idx =
          vtkStructuredData::ComputePointIdForExtent(ghostedExtent, ijk, this->DataDescription);

        if (this->IsNodeWithinExtent(i, j, k, gridExtent))
        {
          // Get index w.r.t. the register extent
          vtkIdType srcidx =
            vtkStructuredData::ComputePointIdForExtent(gridExtent, ijk, this->DataDescription);
          if (this->GridPointGhostArrays[gridID])
          {
            p = this->GridPointGhostArrays[gridID]->GetValue(srcidx);
            this->GhostedPointGhostArray[gridID]->SetValue(idx, p);
          }
        }
        else
        {
          p = 0;

          if (this->IsNodeOnBoundary(i, j, k))
          {
            // We might use BOUNDARY in the future
          }
          p |= vtkDataSetAttributes::DUPLICATEPOINT;
          this->GhostedPointGhostArray[gridID]->SetValue(idx, p);
        }
      } // END for all k
    }   // END for all j
  }     // END for all i

  // STEP 5: Fill the cells ghost arrays for the ghosted grid
  int dim = vtkStructuredData::GetDataDimension(this->DataDescription);
  assert("pre: data dimensions must be 1, 2 or 3" && (dim >= 1) && (dim <= 3));

  int dims[3];
  vtkStructuredData::GetDimensionsFromExtent(ghostedExtent, dims);

  int numNodesPerCell = this->GetNumberOfNodesPerCell(dim);

  int CellExtent[6];
  vtkStructuredData::GetCellExtentFromPointExtent(ghostedExtent, CellExtent);

  this->FillCellsGhostArray(this->DataDescription, numNodesPerCell, dims, CellExtent,
    this->GhostedPointGhostArray[gridID], this->GhostedCellGhostArray[gridID]);
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::AllocatePointData(
  vtkPointData* RPD, const int N, vtkPointData* PD)
{
  assert("pre: Reference point data is nullptr" && (RPD != nullptr));
  assert("pre: point data is nullptr" && (PD != nullptr));
  assert("pre: N > 0" && (N > 0));

  for (int array = 0; array < RPD->GetNumberOfArrays(); ++array)
  {
    int dataType = RPD->GetArray(array)->GetDataType();
    vtkDataArray* dataArray = vtkDataArray::CreateDataArray(dataType);
    assert("Cannot create data array" && (dataArray != nullptr));

    dataArray->SetName(RPD->GetArray(array)->GetName());
    dataArray->SetNumberOfComponents(RPD->GetArray(array)->GetNumberOfComponents());
    dataArray->SetNumberOfTuples(N);

    PD->AddArray(dataArray);
    dataArray->Delete();
  } // END for all arrays
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::AllocateCellData(vtkCellData* RCD, const int N, vtkCellData* CD)
{
  assert("pre: Reference cell data is nullptr" && (RCD != nullptr));
  assert("pre: cell data is nullptr" && (CD != nullptr));
  assert("pre: N > 0" && (N > 0));

  for (int array = 0; array < RCD->GetNumberOfArrays(); ++array)
  {
    int dataType = RCD->GetArray(array)->GetDataType();
    vtkDataArray* dataArray = vtkDataArray::CreateDataArray(dataType);
    assert("Cannot create data array" && (dataArray != nullptr));

    dataArray->SetName(RCD->GetArray(array)->GetName());
    dataArray->SetNumberOfComponents(RCD->GetArray(array)->GetNumberOfComponents());
    dataArray->SetNumberOfTuples(N);

    CD->AddArray(dataArray);
    dataArray->Delete();
  } // END for all arrays
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::InitializeGhostData(const int gridID)
{
  // Sanity check
  assert("pre: gridID is out-of-bounds!" && (gridID >= 0) &&
    (gridID < static_cast<int>(this->NumberOfGrids)));
  assert("pre: GhostedPointData vector has not been properly allocated!" &&
    (this->NumberOfGrids == this->GhostedGridPointData.size()));
  assert("pre: GhostedCellData vector has not been properly allocated!" &&
    (this->NumberOfGrids == this->GhostedGridCellData.size()));
  assert("pre: Grid has no registered point data!" && (this->GridPointData[gridID] != nullptr));
  assert("pre: Grid has no registered cell data!" && (this->GridCellData[gridID] != nullptr));

  // STEP 0: Get the ghosted grid extent
  int GhostedGridExtent[6];
  this->GetGhostedGridExtent(gridID, GhostedGridExtent);

  // STEP 1: Get the number of nodes/cells in the ghosted extent
  int numNodes = vtkStructuredData::GetNumberOfPoints(GhostedGridExtent, this->DataDescription);
  int numCells = vtkStructuredData::GetNumberOfCells(GhostedGridExtent, this->DataDescription);

  // STEP 2: Allocate coordinates if the grid
  if (this->GridPoints[gridID] != nullptr)
  {

    if (this->GhostedGridPoints[gridID] != nullptr)
    {
      this->GhostedGridPoints[gridID]->Delete();
    }

    this->GhostedGridPoints[gridID] = vtkPoints::New();
    this->GhostedGridPoints[gridID]->SetDataTypeToDouble();
    this->GhostedGridPoints[gridID]->SetNumberOfPoints(numNodes);
  }

  // STEP 3: Allocate point & cell data
  this->GhostedGridPointData[gridID] = vtkPointData::New();
  this->GhostedGridCellData[gridID] = vtkCellData::New();

  this->AllocatePointData(
    this->GridPointData[gridID], numNodes, this->GhostedGridPointData[gridID]);
  this->AllocateCellData(this->GridCellData[gridID], numCells, this->GhostedGridCellData[gridID]);
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::CopyCoordinates(
  vtkPoints* source, vtkIdType sourceIdx, vtkPoints* target, vtkIdType targetIdx)
{
  assert("pre: source points is nullptr" && (source != nullptr));
  assert("pre: target points is nullptr" && (target != nullptr));
  assert("pre: source index is out-of-bounds!" && (sourceIdx >= 0) &&
    (sourceIdx < source->GetNumberOfPoints()));
  assert("pre: target index is out-of-bounds!" && (targetIdx >= 0) &&
    (targetIdx < target->GetNumberOfPoints()));
  target->SetPoint(targetIdx, source->GetPoint(sourceIdx));
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::CopyFieldData(
  vtkFieldData* source, vtkIdType sourceIdx, vtkFieldData* target, vtkIdType targetIdx)
{
  assert("pre: source field data is nullptr!" && (source != nullptr));
  assert("pre: target field data is nullptr!" && (target != nullptr));
  assert("pre: source number of arrays does not match target!" &&
    source->GetNumberOfArrays() == target->GetNumberOfArrays());

  int arrayIdx = 0;
  for (; arrayIdx < source->GetNumberOfArrays(); ++arrayIdx)
  {
    // Get source array
    vtkDataArray* sourceArray = source->GetArray(arrayIdx);
    assert("ERROR: encountered nullptr source array" && (sourceArray != nullptr));

    // Get target array
    vtkDataArray* targetArray = target->GetArray(arrayIdx);
    assert("ERROR: encountered nullptr target array" && (targetArray != nullptr));

    // Sanity checks
    assert("ERROR: target/source array name mismatch!" &&
      (strcmp(sourceArray->GetName(), targetArray->GetName()) == 0));
    assert("ERROR: target/source array num components mismatch!" &&
      (sourceArray->GetNumberOfComponents() == targetArray->GetNumberOfComponents()));
    assert("ERROR: sourceIdx out-of-bounds!" && (sourceIdx >= 0) &&
      (sourceIdx < sourceArray->GetNumberOfTuples()));
    assert("ERROR: targetIdx out-of-bounds!" && (targetIdx >= 0) &&
      (targetIdx < targetArray->GetNumberOfTuples()));

    // Copy the tuple
    targetArray->SetTuple(targetIdx, sourceIdx, sourceArray);
  } // END for all arrays
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::TransferRegisteredDataToGhostedData(const int gridID)
{
  // Sanity check
  assert("pre: gridID is out-of-bounds!" && (gridID >= 0) &&
    (gridID < static_cast<int>(this->NumberOfGrids)));

  // STEP 0: Get the registered grid extent
  int GridExtent[6];
  this->GetGridExtent(gridID, GridExtent);

  // STEP 1: Get the registered grid cell extent
  int GridCellExtent[6];
  vtkStructuredData::GetCellExtentFromPointExtent(
    GridExtent, GridCellExtent, this->DataDescription);

  // STEP 2: Get the ghosted grid extent
  int GhostedGridExtent[6];
  this->GetGhostedGridExtent(gridID, GhostedGridExtent);

  // STEP 3: Get the ghosted grid cell extent
  int GhostedGridCellExtent[6];
  vtkStructuredData::GetCellExtentFromPointExtent(
    GhostedGridExtent, GhostedGridCellExtent, this->DataDescription);

  // STEP 2: Loop over the registered grid extent
  int ijk[3];
  for (int i = GridExtent[0]; i <= GridExtent[1]; ++i)
  {
    for (int j = GridExtent[2]; j <= GridExtent[3]; ++j)
    {
      for (int k = GridExtent[4]; k <= GridExtent[5]; ++k)
      {

        ijk[0] = i;
        ijk[1] = j;
        ijk[2] = k;

        // Compute the source index to the registered data
        vtkIdType sourceIdx =
          vtkStructuredData::ComputePointIdForExtent(GridExtent, ijk, this->DataDescription);

        // Compute the target index to the ghosted data
        vtkIdType targetIdx =
          vtkStructuredData::ComputePointIdForExtent(GhostedGridExtent, ijk, this->DataDescription);

        if (this->GridPoints[gridID] != nullptr)
        {
          this->CopyCoordinates(
            this->GridPoints[gridID], sourceIdx, this->GhostedGridPoints[gridID], targetIdx);
        } // END if grid points is not nullptr

        // Transfer node data from the registered grid to the ghosted grid
        this->CopyFieldData(
          this->GridPointData[gridID], sourceIdx, this->GhostedGridPointData[gridID], targetIdx);

        // If the node is within the cell extent, copy the cell datta
        if (this->IsNodeWithinExtent(i, j, k, GridCellExtent))
        {
          // Compute the source cell idx. Note, since we are passing to
          // ComputePointIdForExtent a cell extent, this is a cell id, not
          // a point id.
          vtkIdType sourceCellIdx =
            vtkStructuredData::ComputePointIdForExtent(GridCellExtent, ijk, this->DataDescription);

          // Compute the target cell idx. Note, since we are passing to
          // ComputePointIdForExtent a cell extent, this is a cell id, not
          // a point id.
          vtkIdType targetCellIdx = vtkStructuredData::ComputePointIdForExtent(
            GhostedGridCellExtent, ijk, this->DataDescription);

          // Transfer cell data from the registered grid to the ghosted grid
          this->CopyFieldData(this->GridCellData[gridID], sourceCellIdx,
            this->GhostedGridCellData[gridID], targetCellIdx);
        } // END if node is within cell extent

      } // END for all k
    }   // END for all j
  }     // END for all i
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::TransferGhostDataFromNeighbors(const int gridID)
{
  // Sanity check
  assert("pre: gridID is out-of-bounds!" && (gridID >= 0) &&
    (gridID < static_cast<int>(this->NumberOfGrids)));
  assert(
    "pre: Neighbors is not properly allocated" && (this->NumberOfGrids == this->Neighbors.size()));

  int NumNeis = static_cast<int>(this->Neighbors[gridID].size());
  for (int nei = 0; nei < NumNeis; ++nei)
  {
    this->TransferLocalNeighborData(gridID, this->Neighbors[gridID][nei]);
  } // END for all neighbors
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::TransferLocalNeighborData(
  const int gridID, const vtkStructuredNeighbor& Neighbor)
{
  // Sanity check
  assert("pre: gridID is out-of-bounds!" && (gridID >= 0) &&
    (gridID < static_cast<int>(this->NumberOfGrids)));
  assert("pre: Neighbor gridID is out-of-bounds!" && (Neighbor.NeighborID >= 0) &&
    (Neighbor.NeighborID < static_cast<int>(this->NumberOfGrids)));

  // STEP 0: Get ghosted grid (node) extent and corresponding cell extent
  int GhostedGridExtent[6];
  this->GetGhostedGridExtent(gridID, GhostedGridExtent);
  int GhostedGridCellExtent[6];
  vtkStructuredData::GetCellExtentFromPointExtent(GhostedGridExtent, GhostedGridCellExtent);

  // STEP 1: Get the neighbor (node) extent and corresponding cell extent
  int NeighborExtent[6];
  this->GetGridExtent(Neighbor.NeighborID, NeighborExtent);
  int NeighborCellExtent[6];
  vtkStructuredData::GetCellExtentFromPointExtent(NeighborExtent, NeighborCellExtent);

  int RcvCellExtent[6];
  vtkStructuredData::GetCellExtentFromPointExtent(
    const_cast<int*>(Neighbor.RcvExtent), RcvCellExtent);

  // STEP 3: Transfer the RcvExtent to the grid from the Neighbor
  int ijk[3];
  for (int i = Neighbor.RcvExtent[0]; i <= Neighbor.RcvExtent[1]; ++i)
  {
    for (int j = Neighbor.RcvExtent[2]; j <= Neighbor.RcvExtent[3]; ++j)
    {
      for (int k = Neighbor.RcvExtent[4]; k <= Neighbor.RcvExtent[5]; ++k)
      {
        // Sanity check!
        assert("pre: RcvExtent is outside the GhostExtent!" &&
          this->IsNodeWithinExtent(i, j, k, GhostedGridExtent));
        assert("pre: RcvExtent is outside the NeighborExtent" &&
          this->IsNodeWithinExtent(i, j, k, NeighborExtent));

        ijk[0] = i;
        ijk[1] = j;
        ijk[2] = k;

        // Compute the source index to the registered neighbor data
        vtkIdType srcIdx =
          vtkStructuredData::ComputePointIdForExtent(NeighborExtent, ijk, this->DataDescription);

        // Compute the target index into the ghosted data
        vtkIdType targetIdx =
          vtkStructuredData::ComputePointIdForExtent(GhostedGridExtent, ijk, this->DataDescription);

        if (this->GridPoints[Neighbor.NeighborID] != nullptr)
        {
          this->CopyCoordinates(this->GridPoints[Neighbor.NeighborID], srcIdx,
            this->GhostedGridPoints[gridID], targetIdx);
        } // END if this

        // Transfer node data from the registered grid to the ghosted grid
        this->CopyFieldData(this->GridPointData[Neighbor.NeighborID], srcIdx,
          this->GhostedGridPointData[gridID], targetIdx);

        if (this->IsNodeWithinExtent(i, j, k, RcvCellExtent))
        {
          // Compute the source cell idx. Note, since we are passing to
          // ComputePointIdForExtent a cell extent, this is a cell id, not
          // a point id.
          vtkIdType sourceCellIdx = vtkStructuredData::ComputePointIdForExtent(
            NeighborCellExtent, ijk, this->DataDescription);

          // Compute the target cell idx. Note, since we are passing to
          // ComputePointIdForExtent a cell extent, this is a cell id, not
          // a point id.
          vtkIdType targetCellIdx = vtkStructuredData::ComputePointIdForExtent(
            GhostedGridCellExtent, ijk, this->DataDescription);

          // Transfer cell data from the registered grid to the ghosted grid
          this->CopyFieldData(this->GridCellData[Neighbor.NeighborID], sourceCellIdx,
            this->GhostedGridCellData[gridID], targetCellIdx);
        } // END if node is within cell extent

      } // END for all k
    }   // END for all j
  }     // END for all i
}

//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::ComputeNeighborSendAndRcvExtent(const int gridID, const int N)
{
  // Sanity check
  assert("pre: gridID is out-of-bounds!" && (gridID >= 0) &&
    (gridID < static_cast<int>(this->NumberOfGrids)));
  assert(
    "pre: Neighbors is not properly allocated" && (this->NumberOfGrids == this->Neighbors.size()));

  int myRealGridExtent[6];
  this->GetGridExtent(gridID, myRealGridExtent);

  int myGhostedGridExtent[6];
  this->GetGhostedGridExtent(gridID, myGhostedGridExtent);

  int NumNeis = static_cast<int>(this->Neighbors[gridID].size());
  for (int nei = 0; nei < NumNeis; ++nei)
  {
    int neiRealExtent[6];
    this->GetGridExtent(this->Neighbors[gridID][nei].NeighborID, neiRealExtent);

    this->Neighbors[gridID][nei].ComputeSendAndReceiveExtent(
      myRealGridExtent, myGhostedGridExtent, neiRealExtent, this->WholeExtent, N);
  }
}
//------------------------------------------------------------------------------
void vtkStructuredGridConnectivity::CreateGhostLayers(const int N)
{
  if (N == 0)
  {
    vtkWarningMacro("N=0 ghost layers requested! No ghost layers will be created");
    return;
  }

  this->NumberOfGhostLayers += N;
  this->AllocateInternalDataStructures();
  this->GhostedExtents.resize(this->NumberOfGrids * 6, -1);

  for (unsigned int i = 0; i < this->NumberOfGrids; ++i)
  {
    this->CreateGhostedExtent(i, N);
    this->CreateGhostedMaskArrays(i);
    this->ComputeNeighborSendAndRcvExtent(i, N);
    this->InitializeGhostData(i);
    this->TransferRegisteredDataToGhostedData(i);
    this->TransferGhostDataFromNeighbors(i);
  } // END for all grids
}
