//============================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt 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.
//============================================================================

#ifndef vtk_m_worklet_Wavelets_h
#define vtk_m_worklet_Wavelets_h

#include <vtkm/worklet/DispatcherMapField.h>
#include <vtkm/worklet/WorkletMapField.h>

#include <vtkm/Math.h>

namespace vtkm
{
namespace worklet
{
namespace wavelets
{

enum DWTMode
{ // boundary extension modes
  SYMH,
  SYMW,
  ASYMH,
  ASYMW
};

enum ExtensionDirection
{         // which side of a cube to extend
  LEFT,   // X direction
  RIGHT,  // X direction     Y
  TOP,    // Y direction     |   Z
  BOTTOM, // Y direction     |  /
  FRONT,  // Z direction     | /
  BACK    // Z direction     |/________ X
};

// Worklet for 3D signal extension
// It operates on a specified part of a big cube
class ExtensionWorklet3D : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayOut, // extension
                                WholeArrayIn); // signal
  using ExecutionSignature = void(_1, _2, WorkIndex);
  using InputDomain = _1;

  // Constructor
  VTKM_EXEC_CONT
  ExtensionWorklet3D(vtkm::Id extdimX,
                     vtkm::Id extdimY,
                     vtkm::Id extdimZ,
                     vtkm::Id sigdimX,
                     vtkm::Id sigdimY,
                     vtkm::Id sigdimZ,
                     vtkm::Id sigstartX,
                     vtkm::Id sigstartY,
                     vtkm::Id sigstartZ,
                     vtkm::Id sigpretendX,
                     vtkm::Id sigpretendY,
                     vtkm::Id sigpretendZ,
                     DWTMode m, // SYMH, SYMW, etc.
                     ExtensionDirection dir,
                     bool pad_zero)
    : extDimX(extdimX)
    , extDimY(extdimY)
    , extDimZ(extdimZ)
    , sigDimX(sigdimX)
    , sigDimY(sigdimY)
    , sigDimZ(sigdimZ)
    , sigStartX(sigstartX)
    , sigStartY(sigstartY)
    , sigStartZ(sigstartZ)
    , sigPretendDimX(sigpretendX)
    , sigPretendDimY(sigpretendY)
    , sigPretendDimZ(sigpretendZ)
    , mode(m)
    , direction(dir)
    , padZero(pad_zero)
  {
    (void)sigDimZ;
  }

  // Index translation helper
  VTKM_EXEC_CONT
  void Ext1Dto3D(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y, vtkm::Id& z) const
  {
    z = idx / (extDimX * extDimY);
    y = (idx - z * extDimX * extDimY) / extDimX;
    x = idx % extDimX;
  }

  // Index translation helper
  VTKM_EXEC_CONT
  vtkm::Id Sig3Dto1D(vtkm::Id x, vtkm::Id y, vtkm::Id z) const
  {
    return z * sigDimX * sigDimY + y * sigDimX + x;
  }

  // Index translation helper
  VTKM_EXEC_CONT
  vtkm::Id SigPretend3Dto1D(vtkm::Id x, vtkm::Id y, vtkm::Id z) const
  {
    return (z + sigStartZ) * sigDimX * sigDimY + (y + sigStartY) * sigDimX + x + sigStartX;
  }

  template <typename PortalOutType, typename PortalInType>
  VTKM_EXEC void operator()(PortalOutType& portalOut,
                            const PortalInType& portalIn,
                            const vtkm::Id& workIndex) const
  {
    vtkm::Id extX, extY, extZ;
    vtkm::Id sigPretendX, sigPretendY, sigPretendZ;
    Ext1Dto3D(workIndex, extX, extY, extZ);
    typename PortalOutType::ValueType sym = 1.0;
    if (mode == ASYMH || mode == ASYMW)
    {
      sym = -1.0;
    }
    if (direction == LEFT)
    {
      sigPretendY = extY;
      sigPretendZ = extZ;
      if (mode == SYMH || mode == ASYMH)
      {
        sigPretendX = extDimX - extX - 1;
      }
      else // mode == SYMW || mode == ASYMW
      {
        sigPretendX = extDimX - extX;
      }
    }
    else if (direction == RIGHT)
    {
      sigPretendY = extY;
      sigPretendZ = extZ;
      if (mode == SYMH || mode == ASYMH)
      {
        sigPretendX = sigPretendDimX - extX - 1;
      }
      else
      {
        sigPretendX = sigPretendDimX - extX - 2;
      }
      if (padZero)
      {
        sigPretendX++;
      }
    }
    else if (direction == TOP)
    {
      sigPretendX = extX;
      sigPretendZ = extZ;
      if (mode == SYMH || mode == ASYMH)
      {
        sigPretendY = extDimY - extY - 1;
      }
      else // mode == SYMW || mode == ASYMW
      {
        sigPretendY = extDimY - extY;
      }
    }
    else if (direction == BOTTOM)
    {
      sigPretendX = extX;
      sigPretendZ = extZ;
      if (mode == SYMH || mode == ASYMH)
      {
        sigPretendY = sigPretendDimY - extY - 1;
      }
      else
      {
        sigPretendY = sigPretendDimY - extY - 2;
      }
      if (padZero)
      {
        sigPretendY++;
      }
    }
    else if (direction == FRONT)
    {
      sigPretendX = extX;
      sigPretendY = extY;
      if (mode == SYMH || mode == ASYMH)
      {
        sigPretendZ = extDimZ - extZ - 1;
      }
      else // mode == SYMW || mode == ASYMW
      {
        sigPretendZ = extDimZ - extZ;
      }
    }
    else if (direction == BACK)
    {
      sigPretendX = extX;
      sigPretendY = extY;
      if (mode == SYMH || mode == ASYMH)
      {
        sigPretendZ = sigPretendDimZ - extZ - 1;
      }
      else
      {
        sigPretendZ = sigPretendDimZ - extZ - 2;
      }
      if (padZero)
      {
        sigPretendZ++;
      }
    }
    else
    {
      sigPretendX = sigPretendY = sigPretendZ = 0; // so the compiler doesn't complain
    }

    if (sigPretendX == sigPretendDimX || // decides to pad a zero
        sigPretendY == sigPretendDimY || sigPretendZ == sigPretendDimZ)
    {
      portalOut.Set(workIndex, 0.0);
    }
    else
    {
      portalOut.Set(workIndex,
                    sym * portalIn.Get(SigPretend3Dto1D(sigPretendX, sigPretendY, sigPretendZ)));
    }
  }

private:
  const vtkm::Id extDimX, extDimY, extDimZ, sigDimX, sigDimY, sigDimZ;
  const vtkm::Id sigStartX, sigStartY, sigStartZ;                // defines a small cube to work on
  const vtkm::Id sigPretendDimX, sigPretendDimY, sigPretendDimZ; // small cube dims
  const DWTMode mode;
  const ExtensionDirection direction;
  const bool padZero; // treat sigIn as having a zero at the end
};

//=============================================================================

//  Y
//
//  |      Z
//  |     /
//  |    /
//  |   /
//  |  /
//  | /
//  |/------------- X
//
// The following 3 classes perform the same functionaliry in 3 directions
class IndexTranslator3CubesLeftRight
{
public:
  IndexTranslator3CubesLeftRight(vtkm::Id x_1,
                                 vtkm::Id y_1,
                                 vtkm::Id z_1,
                                 vtkm::Id x_2,
                                 vtkm::Id y_2,
                                 vtkm::Id z_2,
                                 vtkm::Id startx_2,
                                 vtkm::Id starty_2,
                                 vtkm::Id startz_2,
                                 vtkm::Id pretendx_2,
                                 vtkm::Id pretendy_2,
                                 vtkm::Id pretendz_2,
                                 vtkm::Id x_3,
                                 vtkm::Id y_3,
                                 vtkm::Id z_3)
    : dimX1(x_1)
    , dimY1(y_1)
    , dimZ1(z_1)
    , dimX2(x_2)
    , dimY2(y_2)
    , dimZ2(z_2)
    , startX2(startx_2)
    , startY2(starty_2)
    , startZ2(startz_2)
    , pretendDimX2(pretendx_2)
    , pretendDimY2(pretendy_2)
    , pretendDimZ2(pretendz_2)
    , dimX3(x_3)
    , dimY3(y_3)
    , dimZ3(z_3)
  {
    (void)dimZ1;
    (void)dimZ2;
    (void)dimZ3;
    (void)pretendDimY2;
    (void)pretendDimZ2;
  }

  VTKM_EXEC_CONT
  void Translate3Dto1D(vtkm::Id inX,
                       vtkm::Id inY,
                       vtkm::Id inZ, // 3D indices as input
                       vtkm::Id& cube,
                       vtkm::Id& idx) const // which cube, and idx of that cube
  {
    if (dimX1 <= inX && inX < (dimX1 + pretendDimX2))
    {
      vtkm::Id inX_local = inX - dimX1;
      cube = 2;
      idx = (inZ + startZ2) * dimX2 * dimY2 + (inY + startY2) * dimX2 + (inX_local + startX2);
    }
    else if (0 <= inX && inX < dimX1)
    {
      cube = 1;
      idx = inZ * dimX1 * dimY1 + inY * dimX1 + inX;
    }
    else if ((dimX1 + pretendDimX2) <= inX && inX < (dimX1 + pretendDimX2 + dimX3))
    {
      vtkm::Id inX_local = inX - dimX1 - pretendDimX2;
      cube = 3;
      idx = inZ * dimX3 * dimY3 + inY * dimX3 + inX_local;
    }
  }

private:
  const vtkm::Id dimX1, dimY1, dimZ1; // left extension
  const vtkm::Id dimX2, dimY2, dimZ2; // actual signal dims
  const vtkm::Id startX2, startY2, startZ2, pretendDimX2, pretendDimY2, pretendDimZ2;
  const vtkm::Id dimX3, dimY3, dimZ3; // right extension
};

class IndexTranslator3CubesTopDown
{
public:
  IndexTranslator3CubesTopDown(vtkm::Id x_1,
                               vtkm::Id y_1,
                               vtkm::Id z_1,
                               vtkm::Id x_2,
                               vtkm::Id y_2,
                               vtkm::Id z_2,
                               vtkm::Id startx_2,
                               vtkm::Id starty_2,
                               vtkm::Id startz_2,
                               vtkm::Id pretendx_2,
                               vtkm::Id pretendy_2,
                               vtkm::Id pretendz_2,
                               vtkm::Id x_3,
                               vtkm::Id y_3,
                               vtkm::Id z_3)
    : dimX1(x_1)
    , dimY1(y_1)
    , dimZ1(z_1)
    , dimX2(x_2)
    , dimY2(y_2)
    , dimZ2(z_2)
    , startX2(startx_2)
    , startY2(starty_2)
    , startZ2(startz_2)
    , pretendDimX2(pretendx_2)
    , pretendDimY2(pretendy_2)
    , pretendDimZ2(pretendz_2)
    , dimX3(x_3)
    , dimY3(y_3)
    , dimZ3(z_3)
  {
    (void)dimZ1;
    (void)dimZ2;
    (void)dimZ3;
    (void)pretendDimX2;
    (void)pretendDimZ2;
  }

  VTKM_EXEC_CONT
  void Translate3Dto1D(vtkm::Id inX,
                       vtkm::Id inY,
                       vtkm::Id inZ, // 3D indices as input
                       vtkm::Id& cube,
                       vtkm::Id& idx) const // which cube, and idx of that cube
  {
    if (dimY1 <= inY && inY < (dimY1 + pretendDimY2))
    {
      vtkm::Id inY_local = inY - dimY1;
      cube = 2;
      idx = (inZ + startZ2) * dimX2 * dimY2 + (inY_local + startY2) * dimX2 + inX + startX2;
    }
    else if (0 <= inY && inY < dimY1)
    {
      cube = 1;
      idx = inZ * dimX1 * dimY1 + inY * dimX1 + inX;
    }
    else if ((dimY1 + pretendDimY2) <= inY && inY < (dimY1 + pretendDimY2 + dimY3))
    {
      vtkm::Id inY_local = inY - dimY1 - pretendDimY2;
      cube = 3;
      idx = inZ * dimX3 * dimY3 + inY_local * dimX3 + inX;
    }
  }

private:
  const vtkm::Id dimX1, dimY1, dimZ1; // left extension
  const vtkm::Id dimX2, dimY2, dimZ2; // actual signal dims
  const vtkm::Id startX2, startY2, startZ2, pretendDimX2, pretendDimY2, pretendDimZ2;
  const vtkm::Id dimX3, dimY3, dimZ3; // right extension
};

class IndexTranslator3CubesFrontBack
{
public:
  IndexTranslator3CubesFrontBack(vtkm::Id x_1,
                                 vtkm::Id y_1,
                                 vtkm::Id z_1,
                                 vtkm::Id x_2,
                                 vtkm::Id y_2,
                                 vtkm::Id z_2,
                                 vtkm::Id startx_2,
                                 vtkm::Id starty_2,
                                 vtkm::Id startz_2,
                                 vtkm::Id pretendx_2,
                                 vtkm::Id pretendy_2,
                                 vtkm::Id pretendz_2,
                                 vtkm::Id x_3,
                                 vtkm::Id y_3,
                                 vtkm::Id z_3)
    : dimX1(x_1)
    , dimY1(y_1)
    , dimZ1(z_1)
    , dimX2(x_2)
    , dimY2(y_2)
    , dimZ2(z_2)
    , startX2(startx_2)
    , startY2(starty_2)
    , startZ2(startz_2)
    , pretendDimX2(pretendx_2)
    , pretendDimY2(pretendy_2)
    , pretendDimZ2(pretendz_2)
    , dimX3(x_3)
    , dimY3(y_3)
    , dimZ3(z_3)
  {
    (void)dimZ2;
    (void)pretendDimX2;
    (void)pretendDimY2;
  }

  VTKM_EXEC_CONT
  void Translate3Dto1D(vtkm::Id inX,
                       vtkm::Id inY,
                       vtkm::Id inZ, // 3D indices as input
                       vtkm::Id& cube,
                       vtkm::Id& idx) const // which cube, and idx of that cube
  {
    if (dimZ1 <= inZ && inZ < (dimZ1 + pretendDimZ2))
    {
      vtkm::Id inZ_local = inZ - dimZ1;
      cube = 2;
      idx = (inZ_local + startZ2) * dimX2 * dimY2 + (inY + startY2) * dimX2 + inX + startX2;
    }
    else if (0 <= inZ && inZ < dimZ1)
    {
      cube = 1;
      idx = inZ * dimX1 * dimY1 + inY * dimX1 + inX;
    }
    else if ((dimZ1 + pretendDimZ2) <= inZ && inZ < (dimZ1 + pretendDimZ2 + dimZ3))
    {
      vtkm::Id inZ_local = inZ - dimZ1 - pretendDimZ2;
      cube = 3;
      idx = inZ_local * dimX3 * dimY3 + inY * dimX3 + inX;
    }
  }

private:
  const vtkm::Id dimX1, dimY1, dimZ1; // left extension
  const vtkm::Id dimX2, dimY2, dimZ2; // actual signal dims
  const vtkm::Id startX2, startY2, startZ2, pretendDimX2, pretendDimY2, pretendDimZ2;
  const vtkm::Id dimX3, dimY3, dimZ3; // right extension
};

//=============================================================================

//
//  ---------------------------------------------------
//  |      |          |      |      |          |      |
//  |cube1 |  cube5   |cube2 |cube3 |  cube5   |cube4 |
//  | ext1 |    cA    | ext2 | ext3 |    cD    | ext4 |
//  | (x1) |   (xa)   | (x2) | (x3) |   (xd)   | (x4) |
//  |      |          |      |      |          |      |
//  ----------------------------------------------------
// The following 3 classes perform the same functionaliry in 3 directions
class IndexTranslator6CubesLeftRight
{
public:
  IndexTranslator6CubesLeftRight(vtkm::Id x_1,
                                 vtkm::Id y_1,
                                 vtkm::Id z_1,
                                 vtkm::Id x_2,
                                 vtkm::Id y_2,
                                 vtkm::Id z_2,
                                 vtkm::Id x_3,
                                 vtkm::Id y_3,
                                 vtkm::Id z_3,
                                 vtkm::Id x_4,
                                 vtkm::Id y_4,
                                 vtkm::Id z_4,
                                 vtkm::Id x_a,
                                 vtkm::Id y_a,
                                 vtkm::Id z_a,
                                 vtkm::Id x_d,
                                 vtkm::Id y_d,
                                 vtkm::Id z_d,
                                 vtkm::Id x_5,
                                 vtkm::Id y_5,
                                 vtkm::Id z_5,
                                 vtkm::Id start_x5,
                                 vtkm::Id start_y5,
                                 vtkm::Id start_z5)
    : dimX1(x_1)
    , dimY1(y_1)
    , dimZ1(z_1)
    , dimX2(x_2)
    , dimY2(y_2)
    , dimZ2(z_2)
    , dimX3(x_3)
    , dimY3(y_3)
    , dimZ3(z_3)
    , dimX4(x_4)
    , dimY4(y_4)
    , dimZ4(z_4)
    , dimXa(x_a)
    , dimYa(y_a)
    , dimZa(z_a)
    , dimXd(x_d)
    , dimYd(y_d)
    , dimZd(z_d)
    , dimX5(x_5)
    , dimY5(y_5)
    , dimZ5(z_5)
    , startX5(start_x5)
    , startY5(start_y5)
    , startZ5(start_z5)
  {
    (void)dimYa;
    (void)dimYd;
    (void)dimZa;
    (void)dimZd;
    (void)dimZ5;
    (void)dimZ4;
    (void)dimZ3;
    (void)dimZ2;
    (void)dimZ1;
  }

  VTKM_EXEC_CONT
  void Translate3Dto1D(vtkm::Id inX,
                       vtkm::Id inY,
                       vtkm::Id inZ, // 3D indices as input
                       vtkm::Id& cube,
                       vtkm::Id& idx) const // which cube, and idx of that cube
  {
    if (dimX1 <= inX && inX < (dimX1 + dimXa))
    {
      vtkm::Id inX_local = inX - dimX1;
      cube = 5; // cAcD
      idx = (inZ + startZ5) * dimX5 * dimY5 + (inY + startY5) * dimX5 + (inX_local + startX5);
    }
    else if ((dimX1 + dimXa + dimX2 + dimX3) <= inX &&
             inX < (dimX1 + dimXa + dimX2 + dimX3 + dimXd))
    {
      vtkm::Id inX_local = inX - dimX1 - dimX2 - dimX3; // no -dimXa since this is on the same cube
      cube = 5;                                         // cAcD
      idx = (inZ + startZ5) * dimX5 * dimY5 + (inY + startY5) * dimX5 + (inX_local + startX5);
    }
    else if (0 <= inX && inX < dimX1)
    {
      cube = 1; // ext1
      idx = inZ * dimX1 * dimY1 + inY * dimX1 + inX;
    }
    else if ((dimX1 + dimXa) <= inX && inX < (dimX1 + dimXa + dimX2))
    {
      vtkm::Id inX_local = inX - dimX1 - dimXa;
      cube = 2; // ext2
      idx = inZ * dimX2 * dimY2 + inY * dimX2 + inX_local;
    }
    else if ((dimX1 + dimXa + dimX2) <= inX && inX < (dimX1 + dimXa + dimX2 + dimX3))
    {
      vtkm::Id inX_local = inX - dimX1 - dimXa - dimX2;
      cube = 3; // ext3
      idx = inZ * dimX3 * dimY3 + inY * dimX3 + inX_local;
    }
    else if ((dimX1 + dimXa + dimX2 + dimX3 + dimXd) <= inX &&
             inX < (dimX1 + dimXa + dimX2 + dimX3 + dimXd + dimX4))
    {
      vtkm::Id inX_local = inX - dimX1 - dimXa - dimX2 - dimX3 - dimXd;
      cube = 4; // ext4
      idx = inZ * dimX4 * dimY4 + inY * dimX4 + inX_local;
    }
  }

private:
  const vtkm::Id dimX1, dimY1, dimZ1, dimX2, dimY2, dimZ2;       // extension cube sizes
  const vtkm::Id dimX3, dimY3, dimZ3, dimX4, dimY4, dimZ4;       // extension cube sizes
  const vtkm::Id dimXa, dimYa, dimZa, dimXd, dimYd, dimZd;       // signal cube sizes
  const vtkm::Id dimX5, dimY5, dimZ5, startX5, startY5, startZ5; // entire cube size
};

class IndexTranslator6CubesTopDown
{
public:
  IndexTranslator6CubesTopDown(vtkm::Id x_1,
                               vtkm::Id y_1,
                               vtkm::Id z_1,
                               vtkm::Id x_2,
                               vtkm::Id y_2,
                               vtkm::Id z_2,
                               vtkm::Id x_3,
                               vtkm::Id y_3,
                               vtkm::Id z_3,
                               vtkm::Id x_4,
                               vtkm::Id y_4,
                               vtkm::Id z_4,
                               vtkm::Id x_a,
                               vtkm::Id y_a,
                               vtkm::Id z_a,
                               vtkm::Id x_d,
                               vtkm::Id y_d,
                               vtkm::Id z_d,
                               vtkm::Id x_5,
                               vtkm::Id y_5,
                               vtkm::Id z_5,
                               vtkm::Id start_x5,
                               vtkm::Id start_y5,
                               vtkm::Id start_z5)
    : dimX1(x_1)
    , dimY1(y_1)
    , dimZ1(z_1)
    , dimX2(x_2)
    , dimY2(y_2)
    , dimZ2(z_2)
    , dimX3(x_3)
    , dimY3(y_3)
    , dimZ3(z_3)
    , dimX4(x_4)
    , dimY4(y_4)
    , dimZ4(z_4)
    , dimXa(x_a)
    , dimYa(y_a)
    , dimZa(z_a)
    , dimXd(x_d)
    , dimYd(y_d)
    , dimZd(z_d)
    , dimX5(x_5)
    , dimY5(y_5)
    , dimZ5(z_5)
    , startX5(start_x5)
    , startY5(start_y5)
    , startZ5(start_z5)
  {
    (void)dimXa;
    (void)dimXd;
    (void)dimZd;
    (void)dimZa;
    (void)dimZ5;
    (void)dimZ4;
    (void)dimZ3;
    (void)dimZ2;
    (void)dimZ1;
  }

  VTKM_EXEC_CONT
  void Translate3Dto1D(vtkm::Id inX,
                       vtkm::Id inY,
                       vtkm::Id inZ, // 3D indices as input
                       vtkm::Id& cube,
                       vtkm::Id& idx) const // which cube, and idx of that cube
  {
    if (dimY1 <= inY && inY < (dimY1 + dimYa))
    {
      vtkm::Id inY_local = inY - dimY1;
      cube = 5; // cAcD
      idx = (inZ + startZ5) * dimX5 * dimY5 + (inY_local + startY5) * dimX5 + (inX + startX5);
    }
    else if ((dimY1 + dimYa + dimY2 + dimY3) <= inY &&
             inY < (dimY1 + dimYa + dimY2 + dimY3 + dimYd))
    {
      vtkm::Id inY_local = inY - dimY1 - dimY2 - dimY3;
      cube = 5; // cAcD
      idx = (inZ + startZ5) * dimX5 * dimY5 + (inY_local + startY5) * dimX5 + (inX + startX5);
    }
    else if (0 <= inY && inY < dimY1)
    {
      cube = 1; // ext1
      idx = inZ * dimX1 * dimY1 + inY * dimX1 + inX;
    }
    else if ((dimY1 + dimYa) <= inY && inY < (dimY1 + dimYa + dimY2))
    {
      vtkm::Id inY_local = inY - dimY1 - dimYa;
      cube = 2; // ext2
      idx = inZ * dimX2 * dimY2 + inY_local * dimX2 + inX;
    }
    else if ((dimY1 + dimYa + dimY2) <= inY && inY < (dimY1 + dimYa + dimY2 + dimY3))
    {
      vtkm::Id inY_local = inY - dimY1 - dimYa - dimY2;
      cube = 3; // ext3
      idx = inZ * dimX3 * dimY3 + inY_local * dimX3 + inX;
    }
    else if ((dimY1 + dimYa + dimY2 + dimY3 + dimYd) <= inY &&
             inY < (dimY1 + dimYa + dimY2 + dimY3 + dimYd + dimY4))
    {
      vtkm::Id inY_local = inY - dimY1 - dimYa - dimY2 - dimY3 - dimYd;
      cube = 4; // ext4
      idx = inZ * dimX4 * dimY4 + inY_local * dimX4 + inX;
    }
  }

private:
  const vtkm::Id dimX1, dimY1, dimZ1, dimX2, dimY2, dimZ2;       // extension cube sizes
  const vtkm::Id dimX3, dimY3, dimZ3, dimX4, dimY4, dimZ4;       // extension cube sizes
  const vtkm::Id dimXa, dimYa, dimZa, dimXd, dimYd, dimZd;       // signal cube sizes
  const vtkm::Id dimX5, dimY5, dimZ5, startX5, startY5, startZ5; // entire cube size
};

class IndexTranslator6CubesFrontBack
{
public:
  VTKM_EXEC_CONT
  IndexTranslator6CubesFrontBack(vtkm::Id x_1,
                                 vtkm::Id y_1,
                                 vtkm::Id z_1,
                                 vtkm::Id x_2,
                                 vtkm::Id y_2,
                                 vtkm::Id z_2,
                                 vtkm::Id x_3,
                                 vtkm::Id y_3,
                                 vtkm::Id z_3,
                                 vtkm::Id x_4,
                                 vtkm::Id y_4,
                                 vtkm::Id z_4,
                                 vtkm::Id x_a,
                                 vtkm::Id y_a,
                                 vtkm::Id z_a,
                                 vtkm::Id x_d,
                                 vtkm::Id y_d,
                                 vtkm::Id z_d,
                                 vtkm::Id x_5,
                                 vtkm::Id y_5,
                                 vtkm::Id z_5,
                                 vtkm::Id start_x5,
                                 vtkm::Id start_y5,
                                 vtkm::Id start_z5)
    : dimX1(x_1)
    , dimY1(y_1)
    , dimZ1(z_1)
    , dimX2(x_2)
    , dimY2(y_2)
    , dimZ2(z_2)
    , dimX3(x_3)
    , dimY3(y_3)
    , dimZ3(z_3)
    , dimX4(x_4)
    , dimY4(y_4)
    , dimZ4(z_4)
    , dimXa(x_a)
    , dimYa(y_a)
    , dimZa(z_a)
    , dimXd(x_d)
    , dimYd(y_d)
    , dimZd(z_d)
    , dimX5(x_5)
    , dimY5(y_5)
    , dimZ5(z_5)
    , startX5(start_x5)
    , startY5(start_y5)
    , startZ5(start_z5)
  {
    (void)dimXd;
    (void)dimXa;
    (void)dimYd;
    (void)dimYa;
    (void)dimZ5;
  }

  VTKM_EXEC_CONT
  void Translate3Dto1D(vtkm::Id inX,
                       vtkm::Id inY,
                       vtkm::Id inZ, // 3D indices as input
                       vtkm::Id& cube,
                       vtkm::Id& idx) const // which cube, and idx of that cube
  {
    if (dimZ1 <= inZ && inZ < (dimZ1 + dimZa))
    {
      vtkm::Id inZ_local = inZ - dimZ1;
      cube = 5; // cAcD
      idx = (inZ_local + startZ5) * dimX5 * dimY5 + (inY + startY5) * dimX5 + (inX + startX5);
    }
    else if ((dimZ1 + dimZa + dimZ2 + dimZ3) <= inZ &&
             inZ < (dimZ1 + dimZa + dimZ2 + dimZ3 + dimZd))
    {
      vtkm::Id inZ_local = inZ - dimZ1 - dimZ2 - dimZ3;
      cube = 5; // cAcD
      idx = (inZ_local + startZ5) * dimX5 * dimY5 + (inY + startY5) * dimX5 + (inX + startX5);
    }
    else if (0 <= inZ && inZ < dimZ1)
    {
      cube = 1; // ext1
      idx = inZ * dimX1 * dimY1 + inY * dimX1 + inX;
    }
    else if ((dimZ1 + dimZa) <= inZ && inZ < (dimZ1 + dimZa + dimZ2))
    {
      vtkm::Id inZ_local = inZ - dimZ1 - dimZa;
      cube = 2; // ext2
      idx = inZ_local * dimX2 * dimY2 + inY * dimX2 + inX;
    }
    else if ((dimZ1 + dimZa + dimZ2) <= inZ && inZ < (dimZ1 + dimZa + dimZ2 + dimZ3))
    {
      vtkm::Id inZ_local = inZ - dimZ1 - dimZa - dimZ2;
      cube = 3; // ext3
      idx = inZ_local * dimX3 * dimY3 + inY * dimX3 + inX;
    }
    else if ((dimZ1 + dimZa + dimZ2 + dimZ3 + dimZd) <= inZ &&
             inZ < (dimZ1 + dimZa + dimZ2 + dimZ3 + dimZd + dimZ4))
    {
      vtkm::Id inZ_local = inZ - dimZ1 - dimZa - dimZ2 - dimZ3 - dimZd;
      cube = 4; // ext4
      idx = inZ_local * dimX4 * dimY4 + inY * dimX4 + inX;
    }
  }

private:
  const vtkm::Id dimX1, dimY1, dimZ1, dimX2, dimY2, dimZ2;       // extension cube sizes
  const vtkm::Id dimX3, dimY3, dimZ3, dimX4, dimY4, dimZ4;       // extension cube sizes
  const vtkm::Id dimXa, dimYa, dimZa, dimXd, dimYd, dimZd;       // signal cube sizes
  const vtkm::Id dimX5, dimY5, dimZ5, startX5, startY5, startZ5; // entire cube size
};

//=============================================================================

// Worklet: 3D forward transform along X (left-right)
class ForwardTransform3DLeftRight : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayIn,   // left extension
                                WholeArrayIn,   // signal
                                WholeArrayIn,   // right extension
                                WholeArrayIn,   // lowFilter
                                WholeArrayIn,   // highFilter
                                WholeArrayOut); // cA followed by cD
  using ExecutionSignature = void(_1, _2, _3, _4, _5, _6, WorkIndex);
  using InputDomain = _6;

  ForwardTransform3DLeftRight(vtkm::Id filter_len,
                              vtkm::Id approx_len,
                              bool odd_low,
                              vtkm::Id dimX1,
                              vtkm::Id dimY1,
                              vtkm::Id dimZ1,
                              vtkm::Id dimX2,
                              vtkm::Id dimY2,
                              vtkm::Id dimZ2,
                              vtkm::Id startX2,
                              vtkm::Id startY2,
                              vtkm::Id startZ2,
                              vtkm::Id pretendX2,
                              vtkm::Id pretendY2,
                              vtkm::Id pretendZ2,
                              vtkm::Id dimX3,
                              vtkm::Id dimY3,
                              vtkm::Id dimZ3)
    : filterLen(filter_len)
    , approxLen(approx_len)
    , outDimX(pretendX2)
    , outDimY(pretendY2)
    //, outDimZ(pretendZ2)
    , translator(dimX1,
                 dimY1,
                 dimZ1,
                 dimX2,
                 dimY2,
                 dimZ2,
                 startX2,
                 startY2,
                 startZ2,
                 pretendX2,
                 pretendY2,
                 pretendZ2,
                 dimX3,
                 dimY3,
                 dimZ3)
  {
    this->lstart = odd_low ? 1 : 0;
    this->hstart = 1;
  }

  VTKM_EXEC_CONT
  void Output1Dto3D(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y, vtkm::Id& z) const
  {
    z = idx / (this->outDimX * this->outDimY);
    y = (idx - z * this->outDimX * this->outDimY) / this->outDimX;
    x = idx % this->outDimX;
  }
  VTKM_EXEC_CONT
  vtkm::Id Output3Dto1D(vtkm::Id x, vtkm::Id y, vtkm::Id z) const
  {
    return z * outDimX * outDimY + y * outDimX + x;
  }

// Use 64-bit float for convolution calculation
#define VAL vtkm::Float64
#define MAKEVAL(a) (static_cast<VAL>(a))
  template <typename InPortalType1, typename InPortalType2, typename InPortalType3>
  VTKM_EXEC_CONT VAL GetVal(const InPortalType1& portal1,
                            const InPortalType2& portal2,
                            const InPortalType3& portal3,
                            vtkm::Id inCube,
                            vtkm::Id inIdx) const
  {
    if (inCube == 2)
    {
      return MAKEVAL(portal2.Get(inIdx));
    }
    else if (inCube == 1)
    {
      return MAKEVAL(portal1.Get(inIdx));
    }
    else if (inCube == 3)
    {
      return MAKEVAL(portal3.Get(inIdx));
    }
    else
    {
      return -1;
    }
  }

  template <typename InPortalType1,
            typename InPortalType2,
            typename InPortalType3,
            typename FilterPortalType,
            typename OutputPortalType>
  VTKM_EXEC_CONT void operator()(const InPortalType1& inPortal1,     // left extension
                                 const InPortalType2& inPortal2,     // signal
                                 const InPortalType3& inPortal3,     // right extension
                                 const FilterPortalType& lowFilter,  // lowFilter
                                 const FilterPortalType& highFilter, // highFilter
                                 OutputPortalType& coeffOut,
                                 const vtkm::Id& workIndex) const
  {
    vtkm::Id workX, workY, workZ, output1D;
    this->Output1Dto3D(workIndex, workX, workY, workZ);
    vtkm::Id inputCube = 0, inputIdx = 0;
    using OutputValueType = typename OutputPortalType::ValueType;

    if (workX % 2 == 0) // calculate cA
    {
      vtkm::Id xl = lstart + workX;
      VAL sum = MAKEVAL(0.0);
      for (vtkm::Id k = filterLen - 1; k > -1; k--)
      {
        translator.Translate3Dto1D(xl, workY, workZ, inputCube, inputIdx);
        sum += lowFilter.Get(k) * GetVal(inPortal1, inPortal2, inPortal3, inputCube, inputIdx);
        xl++;
      }
      output1D = Output3Dto1D(workX / 2, workY, workZ);
      coeffOut.Set(output1D, static_cast<OutputValueType>(sum));
    }
    else // calculate cD
    {
      vtkm::Id xh = hstart + workX - 1;
      VAL sum = MAKEVAL(0.0);
      for (vtkm::Id k = filterLen - 1; k > -1; k--)
      {
        translator.Translate3Dto1D(xh, workY, workZ, inputCube, inputIdx);
        sum += highFilter.Get(k) * GetVal(inPortal1, inPortal2, inPortal3, inputCube, inputIdx);
        xh++;
      }
      output1D = Output3Dto1D((workX - 1) / 2 + approxLen, workY, workZ);
      coeffOut.Set(output1D, static_cast<OutputValueType>(sum));
    }
  }
#undef MAKEVAL
#undef VAL

private:
  const vtkm::Id filterLen, approxLen;
  const vtkm::Id outDimX;
  const vtkm::Id outDimY;
  //const vtkm::Id outDimZ; Not used
  const IndexTranslator3CubesLeftRight translator;
  vtkm::Id lstart, hstart;
};

class ForwardTransform3DTopDown : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayIn,   // left extension
                                WholeArrayIn,   // signal
                                WholeArrayIn,   // right extension
                                WholeArrayIn,   // lowFilter
                                WholeArrayIn,   // highFilter
                                WholeArrayOut); // cA followed by cD
  using ExecutionSignature = void(_1, _2, _3, _4, _5, _6, WorkIndex);
  using InputDomain = _6;

  ForwardTransform3DTopDown(vtkm::Id filter_len,
                            vtkm::Id approx_len,
                            bool odd_low,
                            vtkm::Id dimX1,
                            vtkm::Id dimY1,
                            vtkm::Id dimZ1,
                            vtkm::Id dimX2,
                            vtkm::Id dimY2,
                            vtkm::Id dimZ2,
                            vtkm::Id startX2,
                            vtkm::Id startY2,
                            vtkm::Id startZ2,
                            vtkm::Id pretendX2,
                            vtkm::Id pretendY2,
                            vtkm::Id pretendZ2,
                            vtkm::Id dimX3,
                            vtkm::Id dimY3,
                            vtkm::Id dimZ3)
    : filterLen(filter_len)
    , approxLen(approx_len)
    , outDimX(pretendX2)
    , outDimY(pretendY2)
    //, outDimZ(pretendZ2)
    , translator(dimX1,
                 dimY1,
                 dimZ1,
                 dimX2,
                 dimY2,
                 dimZ2,
                 startX2,
                 startY2,
                 startZ2,
                 pretendX2,
                 pretendY2,
                 pretendZ2,
                 dimX3,
                 dimY3,
                 dimZ3)
  {
    this->lstart = odd_low ? 1 : 0;
    this->hstart = 1;
  }

  VTKM_EXEC_CONT
  void Output1Dto3D(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y, vtkm::Id& z) const
  {
    z = idx / (outDimX * outDimY);
    y = (idx - z * outDimX * outDimY) / outDimX;
    x = idx % outDimX;
  }
  VTKM_EXEC_CONT
  vtkm::Id Output3Dto1D(vtkm::Id x, vtkm::Id y, vtkm::Id z) const
  {
    return z * outDimX * outDimY + y * outDimX + x;
  }

// Use 64-bit float for convolution calculation
#define VAL vtkm::Float64
#define MAKEVAL(a) (static_cast<VAL>(a))
  template <typename InPortalType1, typename InPortalType2, typename InPortalType3>
  VTKM_EXEC_CONT VAL GetVal(const InPortalType1& portal1,
                            const InPortalType2& portal2,
                            const InPortalType3& portal3,
                            vtkm::Id inCube,
                            vtkm::Id inIdx) const
  {
    if (inCube == 2)
    {
      return MAKEVAL(portal2.Get(inIdx));
    }
    else if (inCube == 1)
    {
      return MAKEVAL(portal1.Get(inIdx));
    }
    else if (inCube == 3)
    {
      return MAKEVAL(portal3.Get(inIdx));
    }
    else
    {
      return -1;
    }
  }

  template <typename InPortalType1,
            typename InPortalType2,
            typename InPortalType3,
            typename FilterPortalType,
            typename OutputPortalType>
  VTKM_EXEC_CONT void operator()(const InPortalType1& inPortal1,     // top extension
                                 const InPortalType2& inPortal2,     // signal
                                 const InPortalType3& inPortal3,     // down extension
                                 const FilterPortalType& lowFilter,  // lowFilter
                                 const FilterPortalType& highFilter, // highFilter
                                 OutputPortalType& coeffOut,
                                 const vtkm::Id& workIndex) const
  {
    vtkm::Id workX, workY, workZ, output1D;
    Output1Dto3D(workIndex, workX, workY, workZ);
    vtkm::Id inputCube = 0, inputIdx = 0;
    using OutputValueType = typename OutputPortalType::ValueType;

    if (workY % 2 == 0) // calculate cA
    {
      vtkm::Id yl = lstart + workY;
      VAL sum = MAKEVAL(0.0);
      for (vtkm::Id k = filterLen - 1; k > -1; k--)
      {
        translator.Translate3Dto1D(workX, yl, workZ, inputCube, inputIdx);
        sum += lowFilter.Get(k) * GetVal(inPortal1, inPortal2, inPortal3, inputCube, inputIdx);
        yl++;
      }
      output1D = Output3Dto1D(workX, workY / 2, workZ);
      coeffOut.Set(output1D, static_cast<OutputValueType>(sum));
    }
    else // calculate cD
    {
      vtkm::Id yh = hstart + workY - 1;
      VAL sum = MAKEVAL(0.0);
      for (vtkm::Id k = filterLen - 1; k > -1; k--)
      {
        translator.Translate3Dto1D(workX, yh, workZ, inputCube, inputIdx);
        sum += highFilter.Get(k) * GetVal(inPortal1, inPortal2, inPortal3, inputCube, inputIdx);
        yh++;
      }
      output1D = Output3Dto1D(workX, (workY - 1) / 2 + approxLen, workZ);
      coeffOut.Set(output1D, static_cast<OutputValueType>(sum));
    }
  }
#undef MAKEVAL
#undef VAL

private:
  const vtkm::Id filterLen, approxLen;
  const vtkm::Id outDimX;
  const vtkm::Id outDimY;
  //const vtkm::Id outDimZ; Not used
  const IndexTranslator3CubesTopDown translator;
  vtkm::Id lstart, hstart;
};

class ForwardTransform3DFrontBack : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayIn,   // left extension
                                WholeArrayIn,   // signal
                                WholeArrayIn,   // right extension
                                WholeArrayIn,   // lowFilter
                                WholeArrayIn,   // highFilter
                                WholeArrayOut); // cA followed by cD
  using ExecutionSignature = void(_1, _2, _3, _4, _5, _6, WorkIndex);
  using InputDomain = _6;

  ForwardTransform3DFrontBack(vtkm::Id filter_len,
                              vtkm::Id approx_len,
                              bool odd_low,
                              vtkm::Id dimX1,
                              vtkm::Id dimY1,
                              vtkm::Id dimZ1,
                              vtkm::Id dimX2,
                              vtkm::Id dimY2,
                              vtkm::Id dimZ2,
                              vtkm::Id startX2,
                              vtkm::Id startY2,
                              vtkm::Id startZ2,
                              vtkm::Id pretendX2,
                              vtkm::Id pretendY2,
                              vtkm::Id pretendZ2,
                              vtkm::Id dimX3,
                              vtkm::Id dimY3,
                              vtkm::Id dimZ3)
    : filterLen(filter_len)
    , approxLen(approx_len)
    , outDimX(pretendX2)
    , outDimY(pretendY2)
    //, outDimZ(pretendZ2)
    , translator(dimX1,
                 dimY1,
                 dimZ1,
                 dimX2,
                 dimY2,
                 dimZ2,
                 startX2,
                 startY2,
                 startZ2,
                 pretendX2,
                 pretendY2,
                 pretendZ2,
                 dimX3,
                 dimY3,
                 dimZ3)
  {
    this->lstart = odd_low ? 1 : 0;
    this->hstart = 1;
  }

  VTKM_EXEC_CONT
  void Output1Dto3D(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y, vtkm::Id& z) const
  {
    z = idx / (outDimX * outDimY);
    y = (idx - z * outDimX * outDimY) / outDimX;
    x = idx % outDimX;
  }
  VTKM_EXEC_CONT
  vtkm::Id Output3Dto1D(vtkm::Id x, vtkm::Id y, vtkm::Id z) const
  {
    return z * outDimX * outDimY + y * outDimX + x;
  }

// Use 64-bit float for convolution calculation
#define VAL vtkm::Float64
#define MAKEVAL(a) (static_cast<VAL>(a))
  template <typename InPortalType1, typename InPortalType2, typename InPortalType3>
  VTKM_EXEC_CONT VAL GetVal(const InPortalType1& portal1,
                            const InPortalType2& portal2,
                            const InPortalType3& portal3,
                            vtkm::Id inCube,
                            vtkm::Id inIdx) const
  {
    if (inCube == 2)
    {
      return MAKEVAL(portal2.Get(inIdx));
    }
    else if (inCube == 1)
    {
      return MAKEVAL(portal1.Get(inIdx));
    }
    else if (inCube == 3)
    {
      return MAKEVAL(portal3.Get(inIdx));
    }
    else
    {
      return -1;
    }
  }

  template <typename InPortalType1,
            typename InPortalType2,
            typename InPortalType3,
            typename FilterPortalType,
            typename OutputPortalType>
  VTKM_EXEC_CONT void operator()(const InPortalType1& inPortal1, // top extension
                                 const InPortalType2& inPortal2, // signal
                                 const InPortalType3& inPortal3, // down extension
                                 const FilterPortalType& lowFilter,
                                 const FilterPortalType& highFilter,
                                 OutputPortalType& coeffOut,
                                 const vtkm::Id& workIndex) const
  {
    vtkm::Id workX, workY, workZ, output1D;
    Output1Dto3D(workIndex, workX, workY, workZ);
    vtkm::Id inputCube = 0, inputIdx = 0;
    using OutputValueType = typename OutputPortalType::ValueType;

    if (workZ % 2 == 0) // calculate cA
    {
      vtkm::Id zl = lstart + workZ;
      VAL sum = MAKEVAL(0.0);
      for (vtkm::Id k = filterLen - 1; k > -1; k--)
      {
        translator.Translate3Dto1D(workX, workY, zl, inputCube, inputIdx);
        sum += lowFilter.Get(k) * GetVal(inPortal1, inPortal2, inPortal3, inputCube, inputIdx);
        zl++;
      }
      output1D = Output3Dto1D(workX, workY, workZ / 2);
      coeffOut.Set(output1D, static_cast<OutputValueType>(sum));
    }
    else // calculate cD
    {
      vtkm::Id zh = hstart + workZ - 1;
      VAL sum = MAKEVAL(0.0);
      for (vtkm::Id k = filterLen - 1; k > -1; k--)
      {
        translator.Translate3Dto1D(workX, workY, zh, inputCube, inputIdx);
        sum += highFilter.Get(k) * GetVal(inPortal1, inPortal2, inPortal3, inputCube, inputIdx);
        zh++;
      }
      output1D = Output3Dto1D(workX, workY, (workZ - 1) / 2 + approxLen);
      coeffOut.Set(output1D, static_cast<OutputValueType>(sum));
    }
  }
#undef MAKEVAL
#undef VAL

private:
  const vtkm::Id filterLen, approxLen;
  const vtkm::Id outDimX;
  const vtkm::Id outDimY;
  //const vtkm::Id outDimZ; Not used
  const IndexTranslator3CubesFrontBack translator;
  vtkm::Id lstart, hstart;
};

//=============================================================================

//
//  ---------------------------------------------------
//  |      |          |      |      |          |      |
//  |cube1 |  cube5   |cube2 |cube3 |  cube5   |cube4 |
//  | ext1 |    cA    | ext2 | ext3 |    cD    | ext4 |
//  | (x1) |   (xa)   | (x2) | (x3) |   (xd)   | (x4) |
//  |      |          |      |      |          |      |
//  ----------------------------------------------------
// The following 3 classes perform the same functionaliry in 3 directions
//
// Worklet: perform a simple 3D inverse transform along X direction (Left-right)
class InverseTransform3DLeftRight : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayIn, // ext1
                                WholeArrayIn, // ext2
                                WholeArrayIn, // ext3
                                WholeArrayIn, // ext4
                                WholeArrayIn, // cA+cD (signal)
                                WholeArrayIn, // lowFilter
                                WholeArrayIn, // highFilter
                                FieldOut);    // outptu coefficients
  using ExecutionSignature = void(_1, _2, _3, _4, _5, _6, _7, _8, WorkIndex);
  using InputDomain = _8;

  // Constructor
  InverseTransform3DLeftRight(vtkm::Id fil_len,
                              vtkm::Id x_1,
                              vtkm::Id y_1,
                              vtkm::Id z_1, // ext1
                              vtkm::Id x_2,
                              vtkm::Id y_2,
                              vtkm::Id z_2, // ext2
                              vtkm::Id x_3,
                              vtkm::Id y_3,
                              vtkm::Id z_3, // ext3
                              vtkm::Id x_4,
                              vtkm::Id y_4,
                              vtkm::Id z_4, // ext4
                              vtkm::Id x_a,
                              vtkm::Id y_a,
                              vtkm::Id z_a, // cA
                              vtkm::Id x_d,
                              vtkm::Id y_d,
                              vtkm::Id z_d, // cD
                              vtkm::Id x_5,
                              vtkm::Id y_5,
                              vtkm::Id z_5, // signal, actual dims
                              vtkm::Id startX5,
                              vtkm::Id startY5,
                              vtkm::Id startZ5)
    : filterLen(fil_len)
    , outDimX(x_a + x_d)
    , outDimY(y_a)
    //, outDimZ(z_a)
    , cALenExtended(x_1 + x_a + x_2)
    , translator(x_1,
                 y_1,
                 z_1,
                 x_2,
                 y_2,
                 z_2,
                 x_3,
                 y_3,
                 z_3,
                 x_4,
                 y_4,
                 z_4,
                 x_a,
                 y_a,
                 z_a,
                 x_d,
                 y_d,
                 z_d,
                 x_5,
                 y_5,
                 z_5,
                 startX5,
                 startY5,
                 startZ5)
  {
  }

  VTKM_EXEC_CONT
  void Output1Dto3D(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y, vtkm::Id& z) const
  {
    z = idx / (outDimX * outDimY);
    y = (idx - z * outDimX * outDimY) / outDimX;
    x = idx % outDimX;
  }

// Use 64-bit float for convolution calculation
#define VAL vtkm::Float64
#define MAKEVAL(a) (static_cast<VAL>(a))
  template <typename InPortalType1,
            typename InPortalType2,
            typename InPortalType3,
            typename InPortalType4,
            typename InPortalType5>
  VTKM_EXEC_CONT VAL GetVal(const InPortalType1& ext1,
                            const InPortalType2& ext2,
                            const InPortalType3& ext3,
                            const InPortalType4& ext4,
                            const InPortalType5& sig5,
                            vtkm::Id inCube,
                            vtkm::Id inIdx) const
  {
    if (inCube == 2)
    {
      return MAKEVAL(ext2.Get(inIdx));
    }
    else if (inCube == 4)
    {
      return MAKEVAL(ext4.Get(inIdx));
    }
    else if (inCube == 1)
    {
      return MAKEVAL(ext1.Get(inIdx));
    }
    else if (inCube == 3)
    {
      return MAKEVAL(ext3.Get(inIdx));
    }
    else if (inCube == 5)
    {
      return MAKEVAL(sig5.Get(inIdx));
    }
    else
    {
      return -1;
    }
  }

  template <typename InPortalType1,
            typename InPortalType2,
            typename InPortalType3,
            typename InPortalType4,
            typename InPortalType5,
            typename FilterPortalType,
            typename OutputValueType>
  VTKM_EXEC void operator()(const InPortalType1& portal1,
                            const InPortalType2& portal2,
                            const InPortalType3& portal3,
                            const InPortalType4& portal4,
                            const InPortalType5& portal5,
                            const FilterPortalType& lowFilter,
                            const FilterPortalType& highFilter,
                            OutputValueType& coeffOut,
                            const vtkm::Id& workIdx) const
  {
    vtkm::Id workX, workY, workZ;
    vtkm::Id k1, k2, xi;
    vtkm::Id inputCube = 0, inputIdx = 0;
    Output1Dto3D(workIdx, workX, workY, workZ);

    if (filterLen % 2 != 0) // odd filter
    {
      if (workX % 2 != 0)
      {
        k1 = filterLen - 2;
        k2 = filterLen - 1;
      }
      else
      {
        k1 = filterLen - 1;
        k2 = filterLen - 2;
      }

      VAL sum = 0.0;
      xi = (workX + 1) / 2;
      while (k1 > -1)
      {
        translator.Translate3Dto1D(xi, workY, workZ, inputCube, inputIdx);
        sum += lowFilter.Get(k1) *
          GetVal(portal1, portal2, portal3, portal4, portal5, inputCube, inputIdx);
        xi++;
        k1 -= 2;
      }
      xi = workX / 2;
      while (k2 > -1)
      {
        translator.Translate3Dto1D(xi + cALenExtended, workY, workZ, inputCube, inputIdx);
        sum += highFilter.Get(k2) *
          GetVal(portal1, portal2, portal3, portal4, portal5, inputCube, inputIdx);
        xi++;
        k2 -= 2;
      }
      coeffOut = static_cast<OutputValueType>(sum);
    }
    else // even filter
    {
      if ((filterLen / 2) % 2 != 0) // odd length half filter
      {
        xi = workX / 2;
        if (workX % 2 != 0)
        {
          k1 = filterLen - 1;
        }
        else
        {
          k1 = filterLen - 2;
        }
      }
      else // even length half filter
      {
        xi = (workX + 1) / 2;
        if (workX % 2 != 0)
        {
          k1 = filterLen - 2;
        }
        else
        {
          k1 = filterLen - 1;
        }
      }
      VAL cA, cD;
      VAL sum = 0.0;
      while (k1 > -1)
      {
        translator.Translate3Dto1D(xi, workY, workZ, inputCube, inputIdx);
        cA = GetVal(portal1, portal2, portal3, portal4, portal5, inputCube, inputIdx);
        translator.Translate3Dto1D(xi + cALenExtended, workY, workZ, inputCube, inputIdx);
        cD = GetVal(portal1, portal2, portal3, portal4, portal5, inputCube, inputIdx);
        sum += lowFilter.Get(k1) * cA + highFilter.Get(k1) * cD;
        xi++;
        k1 -= 2;
      }
      coeffOut = static_cast<OutputValueType>(sum);
    }
  }
#undef MAKEVAL
#undef VAL

private:
  const vtkm::Id filterLen;
  vtkm::Id outDimX;
  vtkm::Id outDimY;
  // vtkm::Id outDimZ; Not used
  vtkm::Id cALenExtended; // Number of cA at the beginning of input, followed by cD
  const IndexTranslator6CubesLeftRight translator;
};

class InverseTransform3DTopDown : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayIn, // ext1
                                WholeArrayIn, // ext2
                                WholeArrayIn, // ext3
                                WholeArrayIn, // ext4
                                WholeArrayIn, // cA+cD (signal)
                                WholeArrayIn, // lowFilter
                                WholeArrayIn, // highFilter
                                FieldOut);    // outptu coefficients
  using ExecutionSignature = void(_1, _2, _3, _4, _5, _6, _7, _8, WorkIndex);
  using InputDomain = _8;

  // Constructor
  InverseTransform3DTopDown(vtkm::Id fil_len,
                            vtkm::Id x_1,
                            vtkm::Id y_1,
                            vtkm::Id z_1, // ext1
                            vtkm::Id x_2,
                            vtkm::Id y_2,
                            vtkm::Id z_2, // ext2
                            vtkm::Id x_3,
                            vtkm::Id y_3,
                            vtkm::Id z_3, // ext3
                            vtkm::Id x_4,
                            vtkm::Id y_4,
                            vtkm::Id z_4, // ext4
                            vtkm::Id x_a,
                            vtkm::Id y_a,
                            vtkm::Id z_a, // cA
                            vtkm::Id x_d,
                            vtkm::Id y_d,
                            vtkm::Id z_d, // cD
                            vtkm::Id x_5,
                            vtkm::Id y_5,
                            vtkm::Id z_5, // signal, actual dims
                            vtkm::Id startX5,
                            vtkm::Id startY5,
                            vtkm::Id startZ5)
    : filterLen(fil_len)
    , outDimX(x_a)
    , outDimY(y_a + y_d)
    //, outDimZ(z_a)
    , cALenExtended(y_1 + y_a + y_2)
    , translator(x_1,
                 y_1,
                 z_1,
                 x_2,
                 y_2,
                 z_2,
                 x_3,
                 y_3,
                 z_3,
                 x_4,
                 y_4,
                 z_4,
                 x_a,
                 y_a,
                 z_a,
                 x_d,
                 y_d,
                 z_d,
                 x_5,
                 y_5,
                 z_5,
                 startX5,
                 startY5,
                 startZ5)
  {
  }

  VTKM_EXEC_CONT
  void Output1Dto3D(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y, vtkm::Id& z) const
  {
    z = idx / (outDimX * outDimY);
    y = (idx - z * outDimX * outDimY) / outDimX;
    x = idx % outDimX;
  }

// Use 64-bit float for convolution calculation
#define VAL vtkm::Float64
#define MAKEVAL(a) (static_cast<VAL>(a))
  template <typename InPortalType1,
            typename InPortalType2,
            typename InPortalType3,
            typename InPortalType4,
            typename InPortalType5>
  VTKM_EXEC_CONT VAL GetVal(const InPortalType1& ext1,
                            const InPortalType2& ext2,
                            const InPortalType3& ext3,
                            const InPortalType4& ext4,
                            const InPortalType5& sig5,
                            vtkm::Id inCube,
                            vtkm::Id inIdx) const
  {
    if (inCube == 2)
    {
      return MAKEVAL(ext2.Get(inIdx));
    }
    else if (inCube == 4)
    {
      return MAKEVAL(ext4.Get(inIdx));
    }
    else if (inCube == 1)
    {
      return MAKEVAL(ext1.Get(inIdx));
    }
    else if (inCube == 3)
    {
      return MAKEVAL(ext3.Get(inIdx));
    }
    else if (inCube == 5)
    {
      return MAKEVAL(sig5.Get(inIdx));
    }
    else
    {
      return -1;
    }
  }

  template <typename InPortalType1,
            typename InPortalType2,
            typename InPortalType3,
            typename InPortalType4,
            typename InPortalType5,
            typename FilterPortalType,
            typename OutputValueType>
  VTKM_EXEC void operator()(const InPortalType1& portal1,
                            const InPortalType2& portal2,
                            const InPortalType3& portal3,
                            const InPortalType4& portal4,
                            const InPortalType5& portal5,
                            const FilterPortalType& lowFilter,
                            const FilterPortalType& highFilter,
                            OutputValueType& coeffOut,
                            const vtkm::Id& workIdx) const
  {
    vtkm::Id workX, workY, workZ;
    vtkm::Id k1, k2, yi;
    vtkm::Id inputCube = 0, inputIdx = 0;
    Output1Dto3D(workIdx, workX, workY, workZ);

    if (filterLen % 2 != 0) // odd filter
    {
      if (workY % 2 != 0)
      {
        k1 = filterLen - 2;
        k2 = filterLen - 1;
      }
      else
      {
        k1 = filterLen - 1;
        k2 = filterLen - 2;
      }

      VAL sum = 0.0;
      yi = (workY + 1) / 2;
      while (k1 > -1)
      {
        translator.Translate3Dto1D(workX, yi, workZ, inputCube, inputIdx);
        sum += lowFilter.Get(k1) *
          GetVal(portal1, portal2, portal3, portal4, portal5, inputCube, inputIdx);
        yi++;
        k1 -= 2;
      }
      yi = workY / 2;
      while (k2 > -1)
      {
        translator.Translate3Dto1D(workX, yi + cALenExtended, workZ, inputCube, inputIdx);
        sum += highFilter.Get(k2) *
          GetVal(portal1, portal2, portal3, portal4, portal5, inputCube, inputIdx);
        yi++;
        k2 -= 2;
      }
      coeffOut = static_cast<OutputValueType>(sum);
    }
    else // even filter
    {
      if ((filterLen / 2) % 2 != 0)
      {
        yi = workY / 2;
        if (workY % 2 != 0)
        {
          k1 = filterLen - 1;
        }
        else
        {
          k1 = filterLen - 2;
        }
      }
      else
      {
        yi = (workY + 1) / 2;
        if (workY % 2 != 0)
        {
          k1 = filterLen - 2;
        }
        else
        {
          k1 = filterLen - 1;
        }
      }
      VAL cA, cD;
      VAL sum = 0.0;
      while (k1 > -1)
      {
        translator.Translate3Dto1D(workX, yi, workZ, inputCube, inputIdx);
        cA = GetVal(portal1, portal2, portal3, portal4, portal5, inputCube, inputIdx);
        translator.Translate3Dto1D(workX, yi + cALenExtended, workZ, inputCube, inputIdx);
        cD = GetVal(portal1, portal2, portal3, portal4, portal5, inputCube, inputIdx);
        sum += lowFilter.Get(k1) * cA + highFilter.Get(k1) * cD;
        yi++;
        k1 -= 2;
      }
      coeffOut = static_cast<OutputValueType>(sum);
    }
  }
#undef MAKEVAL
#undef VAL

private:
  const vtkm::Id filterLen;
  vtkm::Id outDimX;
  vtkm::Id outDimY;
  //vtkm::Id outDimZ; Not used
  vtkm::Id cALenExtended; // Number of cA at the beginning of input, followed by cD
  const IndexTranslator6CubesTopDown translator;
};

class InverseTransform3DFrontBack : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayIn, // ext1
                                WholeArrayIn, // ext2
                                WholeArrayIn, // ext3
                                WholeArrayIn, // ext4
                                WholeArrayIn, // cA+cD (signal)
                                WholeArrayIn, // lowFilter
                                WholeArrayIn, // highFilter
                                FieldOut);    // outptu coefficients
  using ExecutionSignature = void(_1, _2, _3, _4, _5, _6, _7, _8, WorkIndex);
  using InputDomain = _8;

  // Constructor
  InverseTransform3DFrontBack(vtkm::Id fil_len,
                              vtkm::Id x_1,
                              vtkm::Id y_1,
                              vtkm::Id z_1, // ext1
                              vtkm::Id x_2,
                              vtkm::Id y_2,
                              vtkm::Id z_2, // ext2
                              vtkm::Id x_3,
                              vtkm::Id y_3,
                              vtkm::Id z_3, // ext3
                              vtkm::Id x_4,
                              vtkm::Id y_4,
                              vtkm::Id z_4, // ext4
                              vtkm::Id x_a,
                              vtkm::Id y_a,
                              vtkm::Id z_a, // cA
                              vtkm::Id x_d,
                              vtkm::Id y_d,
                              vtkm::Id z_d, // cD
                              vtkm::Id x_5,
                              vtkm::Id y_5,
                              vtkm::Id z_5, // signal, actual dims
                              vtkm::Id startX5,
                              vtkm::Id startY5,
                              vtkm::Id startZ5)
    : filterLen(fil_len)
    , outDimX(x_a)
    , outDimY(y_a)
    //, outDimZ(z_a + z_d)
    , cALenExtended(z_1 + z_a + z_2)
    , translator(x_1,
                 y_1,
                 z_1,
                 x_2,
                 y_2,
                 z_2,
                 x_3,
                 y_3,
                 z_3,
                 x_4,
                 y_4,
                 z_4,
                 x_a,
                 y_a,
                 z_a,
                 x_d,
                 y_d,
                 z_d,
                 x_5,
                 y_5,
                 z_5,
                 startX5,
                 startY5,
                 startZ5)
  {
    /* printf("InverseTransform3DFrontBack: \n");
    printf("  output dims: (%lld, %lld, %lld)\n", outDimX, outDimY, outDimZ ); */
  }

  VTKM_EXEC_CONT
  void Output1Dto3D(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y, vtkm::Id& z) const
  {
    z = idx / (outDimX * outDimY);
    y = (idx - z * outDimX * outDimY) / outDimX;
    x = idx % outDimX;
  }

// Use 64-bit float for convolution calculation
#define VAL vtkm::Float64
#define MAKEVAL(a) (static_cast<VAL>(a))
  template <typename InPortalType1,
            typename InPortalType2,
            typename InPortalType3,
            typename InPortalType4,
            typename InPortalType5>
  VTKM_EXEC_CONT VAL GetVal(const InPortalType1& ext1,
                            const InPortalType2& ext2,
                            const InPortalType3& ext3,
                            const InPortalType4& ext4,
                            const InPortalType5& sig5,
                            vtkm::Id inCube,
                            vtkm::Id inIdx) const
  {
    if (inCube == 2)
    {
      return MAKEVAL(ext2.Get(inIdx));
    }
    else if (inCube == 4)
    {
      return MAKEVAL(ext4.Get(inIdx));
    }
    else if (inCube == 1)
    {
      return MAKEVAL(ext1.Get(inIdx));
    }
    else if (inCube == 3)
    {
      return MAKEVAL(ext3.Get(inIdx));
    }
    else if (inCube == 5)
    {
      return MAKEVAL(sig5.Get(inIdx));
    }
    else
    {
      return -1;
    }
  }

  template <typename InPortalType1,
            typename InPortalType2,
            typename InPortalType3,
            typename InPortalType4,
            typename InPortalType5,
            typename FilterPortalType,
            typename OutputValueType>
  VTKM_EXEC void operator()(const InPortalType1& portal1,
                            const InPortalType2& portal2,
                            const InPortalType3& portal3,
                            const InPortalType4& portal4,
                            const InPortalType5& portal5,
                            const FilterPortalType& lowFilter,
                            const FilterPortalType& highFilter,
                            OutputValueType& coeffOut,
                            const vtkm::Id& workIdx) const
  {
    vtkm::Id workX, workY, workZ;
    vtkm::Id k1, k2, zi;
    vtkm::Id inputCube = 0, inputIdx = 0;
    Output1Dto3D(workIdx, workX, workY, workZ);

    if (filterLen % 2 != 0) // odd filter
    {
      if (workZ % 2 != 0)
      {
        k1 = filterLen - 2;
        k2 = filterLen - 1;
      }
      else
      {
        k1 = filterLen - 1;
        k2 = filterLen - 2;
      }

      VAL sum = 0.0;
      zi = (workZ + 1) / 2;
      while (k1 > -1)
      {
        translator.Translate3Dto1D(workX, workY, zi, inputCube, inputIdx);
        sum += lowFilter.Get(k1) *
          GetVal(portal1, portal2, portal3, portal4, portal5, inputCube, inputIdx);
        zi++;
        k1 -= 2;
      }
      zi = workZ / 2;
      while (k2 > -1)
      {
        translator.Translate3Dto1D(workX, workY, zi + cALenExtended, inputCube, inputIdx);
        sum += highFilter.Get(k2) *
          GetVal(portal1, portal2, portal3, portal4, portal5, inputCube, inputIdx);
        zi++;
        k2 -= 2;
      }
      coeffOut = static_cast<OutputValueType>(sum);
    }
    else // even filter
    {
      if ((filterLen / 2) % 2 != 0)
      {
        zi = workZ / 2;
        if (workZ % 2 != 0)
        {
          k1 = filterLen - 1;
        }
        else
        {
          k1 = filterLen - 2;
        }
      }
      else
      {
        zi = (workZ + 1) / 2;
        if (workZ % 2 != 0)
        {
          k1 = filterLen - 2;
        }
        else
        {
          k1 = filterLen - 1;
        }
      }
      VAL cA, cD;
      VAL sum = 0.0;
      while (k1 > -1)
      {
        translator.Translate3Dto1D(workX, workY, zi, inputCube, inputIdx);
        cA = GetVal(portal1, portal2, portal3, portal4, portal5, inputCube, inputIdx);
        translator.Translate3Dto1D(workX, workY, zi + cALenExtended, inputCube, inputIdx);
        cD = GetVal(portal1, portal2, portal3, portal4, portal5, inputCube, inputIdx);
        sum += lowFilter.Get(k1) * cA + highFilter.Get(k1) * cD;
        zi++;
        k1 -= 2;
      }
      coeffOut = static_cast<OutputValueType>(sum);
    }
  }
#undef MAKEVAL
#undef VAL

private:
  const vtkm::Id filterLen;
  vtkm::Id outDimX;
  vtkm::Id outDimY;
  //vtkm::Id outDimZ; Not used
  vtkm::Id cALenExtended; // Number of cA at the beginning of input, followed by cD
  const IndexTranslator6CubesFrontBack translator;
};

//=============================================================================

//  ---------------------------------------------------
//  |      |          |      |      |          |      |
//  |      |          |      |      |          |      |
//  | ext1 |    cA    | ext2 | ext3 |    cD    | ext4 |
//  | (x1) |   (xa)   | (x2) | (x3) |   (xd)   | (x4) |
//  |      |          |      |      |          |      |
//  ----------------------------------------------------
//  matrix1: ext1
//  matrix2: ext2
//  matrix3: ext3
//  matrix4: ext4
//  matrix5: cA + cD
class IndexTranslator6Matrices
{
public:
  IndexTranslator6Matrices(vtkm::Id x_1,
                           vtkm::Id y_1,
                           vtkm::Id x_a,
                           vtkm::Id y_a,
                           vtkm::Id x_2,
                           vtkm::Id y_2,
                           vtkm::Id x_3,
                           vtkm::Id y_3,
                           vtkm::Id x_d,
                           vtkm::Id y_d,
                           vtkm::Id x_4,
                           vtkm::Id y_4,
                           vtkm::Id x_5,
                           vtkm::Id y_5, // actual size of matrix5
                           vtkm::Id start_x5,
                           vtkm::Id start_y5, // start indices of pretend matrix
                           bool mode)
    : x1(x_1)
    , y1(y_1)
    , xa(x_a)
    , ya(y_a)
    , x2(x_2)
    , y2(y_2)
    , x3(x_3)
    , y3(y_3)
    , xd(x_d)
    , yd(y_d)
    , x4(x_4)
    , y4(y_4)
    , x5(x_5)
    , y5(y_5)
    , startX5(start_x5)
    , startY5(start_y5)
    , modeLR(mode)
  {
    // Get pretend matrix dims
    if (modeLR)
    {
      pretendX5 = xa + xd;
      pretendY5 = y1;
    }
    else
    {
      pretendX5 = x1;
      pretendY5 = ya + yd;
    }
    (void)y5;
  }

  VTKM_EXEC_CONT
  void Translate2Dto1D(vtkm::Id inX,
                       vtkm::Id inY, // 2D indices as input
                       vtkm::Id& mat,
                       vtkm::Id& idx) const // which matrix, and idx of that matrix
  {
    if (modeLR) // left-right mode
    {
      if (0 <= inX && inX < x1)
      {
        mat = 1; // ext1
        idx = inY * x1 + inX;
      }
      else if (x1 <= inX && inX < (x1 + xa))
      {
        mat = 5; // cAcD
        idx = (inY + startY5) * x5 + (inX - x1 + startX5);
      }
      else if ((x1 + xa) <= inX && inX < (x1 + xa + x2))
      {
        mat = 2; // ext2
        idx = inY * x2 + (inX - x1 - xa);
      }
      else if ((x1 + xa + x2) <= inX && inX < (x1 + xa + x2 + x3))
      {
        mat = 3; // ext3
        idx = inY * x3 + (inX - x1 - xa - x2);
      }
      else if ((x1 + xa + x2 + x3) <= inX && inX < (x1 + xa + x2 + x3 + xd))
      {
        mat = 5; // cAcD
        idx = (inY + startY5) * x5 + (inX - x1 - x2 - x3 + startX5);
      }
      else if ((x1 + xa + x2 + x3 + xd) <= inX && inX < (x1 + xa + x2 + x3 + xd + x4))
      {
        mat = 4; // ext4
        idx = inY * x4 + (inX - x1 - xa - x2 - x3 - xd);
      }
    }
    else // top-down mode
    {
      if (0 <= inY && inY < y1)
      {
        mat = 1; // ext1
        idx = inY * x1 + inX;
      }
      else if (y1 <= inY && inY < (y1 + ya))
      {
        mat = 5; // cAcD
        idx = (inY - y1 + startY5) * x5 + inX + startX5;
      }
      else if ((y1 + ya) <= inY && inY < (y1 + ya + y2))
      {
        mat = 2; // ext2
        idx = (inY - y1 - ya) * x2 + inX;
      }
      else if ((y1 + ya + y2) <= inY && inY < (y1 + ya + y2 + y3))
      {
        mat = 3; // ext3
        idx = (inY - y1 - ya - y2) * x3 + inX;
      }
      else if ((y1 + ya + y2 + y3) <= inY && inY < (y1 + ya + y2 + y3 + yd))
      {
        mat = 5; // cAcD
        idx = (inY - y1 - y2 - y3 + startY5) * x5 + inX + startX5;
      }
      else if ((y1 + ya + y2 + y3 + yd) <= inY && inY < (y1 + ya + y2 + y3 + yd + y4))
      {
        mat = 4; // ext4
        idx = (inY - y1 - ya - y2 - y3 - yd) * x4 + inX;
      }
    }
  }

private:
  const vtkm::Id x1, y1, xa, ya, x2, y2, x3, y3, xd, yd, x4, y4;
  vtkm::Id x5, y5, startX5, startY5, pretendX5, pretendY5;
  const bool modeLR; // true = left-right mode; false = top-down mode.
};

//       ................
//       .              .
//  -----.--------------.-----
//  |    . |          | .    |
//  |    . |          | .    |
//  | ext1 |   mat2   | ext2 |
//  | (x1) |   (x2)   | (x3) |
//  |    . |          | .    |
//  -----.--------------.-----
//       ................
class IndexTranslator3Matrices
{
public:
  IndexTranslator3Matrices(vtkm::Id x_1,
                           vtkm::Id y_1,
                           vtkm::Id x_2,
                           vtkm::Id y_2, // actual dims of mat2
                           vtkm::Id startx_2,
                           vtkm::Id starty_2, // start idx of pretend
                           vtkm::Id pretendx_2,
                           vtkm::Id pretendy_2, // pretend dims
                           vtkm::Id x_3,
                           vtkm::Id y_3,
                           bool mode)
    : dimX1(x_1)
    , dimY1(y_1)
    , dimX2(x_2)
    , dimY2(y_2)
    , startX2(startx_2)
    , startY2(starty_2)
    , pretendDimX2(pretendx_2)
    , pretendDimY2(pretendy_2)
    , dimX3(x_3)
    , dimY3(y_3)
    , mode_lr(mode)
  {
    (void)dimY2;
  }

  VTKM_EXEC_CONT
  void Translate2Dto1D(vtkm::Id inX,
                       vtkm::Id inY, // 2D indices as input
                       vtkm::Id& mat,
                       vtkm::Id& idx) const // which matrix, and idx of that matrix
  {
    if (mode_lr) // left-right mode
    {
      if (0 <= inX && inX < dimX1)
      {
        mat = 1;
        idx = inY * dimX1 + inX;
      }
      else if (dimX1 <= inX && inX < (dimX1 + pretendDimX2))
      {
        mat = 2;
        idx = (inY + startY2) * dimX2 + (inX + startX2 - dimX1);
      }
      else if ((dimX1 + pretendDimX2) <= inX && inX < (dimX1 + pretendDimX2 + dimX3))
      {
        mat = 3;
        idx = inY * dimX3 + (inX - dimX1 - pretendDimX2);
      }
    }
    else // top-down mode
    {
      if (0 <= inY && inY < dimY1)
      {
        mat = 1;
        idx = inY * dimX1 + inX;
      }
      else if (dimY1 <= inY && inY < (dimY1 + pretendDimY2))
      {
        mat = 2;
        idx = (inY + startY2 - dimY1) * dimX2 + inX + startX2;
      }
      else if ((dimY1 + pretendDimY2) <= inY && inY < (dimY1 + pretendDimY2 + dimY3))
      {
        mat = 3;
        idx = (inY - dimY1 - pretendDimY2) * dimX3 + inX;
      }
    }
  }

private:
  const vtkm::Id dimX1, dimY1;
  const vtkm::Id dimX2, dimY2, startX2, startY2, pretendDimX2, pretendDimY2;
  const vtkm::Id dimX3, dimY3;
  const bool mode_lr; // true: left right mode; false: top down mode.
};

// Worklet for 2D signal extension
// This implementation operates on a specified part of a big rectangle
class ExtensionWorklet2D : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayOut, // extension part
                                WholeArrayIn); // signal part
  using ExecutionSignature = void(_1, _2, WorkIndex);
  using InputDomain = _1;

  // Constructor
  VTKM_EXEC_CONT
  ExtensionWorklet2D(vtkm::Id extdimX,
                     vtkm::Id extdimY,
                     vtkm::Id sigdimX,
                     vtkm::Id sigdimY,
                     vtkm::Id sigstartX,
                     vtkm::Id sigstartY,
                     vtkm::Id sigpretendX,
                     vtkm::Id sigpretendY,
                     DWTMode m,
                     ExtensionDirection dir,
                     bool pad_zero)
    : extDimX(extdimX)
    , extDimY(extdimY)
    , sigDimX(sigdimX)
    , sigDimY(sigdimY)
    , sigStartX(sigstartX)
    , sigStartY(sigstartY)
    , sigPretendDimX(sigpretendX)
    , sigPretendDimY(sigpretendY)
    , mode(m)
    , direction(dir)
    , padZero(pad_zero)
  {
    (void)sigDimY;
  }

  // Index translation helper
  VTKM_EXEC_CONT
  void Ext1Dto2D(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y) const
  {
    x = idx % extDimX;
    y = idx / extDimX;
  }

  // Index translation helper
  VTKM_EXEC_CONT
  vtkm::Id Sig2Dto1D(vtkm::Id x, vtkm::Id y) const { return y * sigDimX + x; }

  // Index translation helper
  VTKM_EXEC_CONT
  vtkm::Id SigPretend2Dto1D(vtkm::Id x, vtkm::Id y) const
  {
    return (y + sigStartY) * sigDimX + x + sigStartX;
  }

  template <typename PortalOutType, typename PortalInType>
  VTKM_EXEC void operator()(PortalOutType& portalOut,
                            const PortalInType& portalIn,
                            const vtkm::Id& workIndex) const
  {
    vtkm::Id extX, extY, sigPretendX, sigPretendY;
    sigPretendX = sigPretendY = 0;
    Ext1Dto2D(workIndex, extX, extY);
    typename PortalOutType::ValueType sym = 1.0;
    if (mode == ASYMH || mode == ASYMW)
    {
      sym = -1.0;
    }
    if (direction == LEFT)
    {
      sigPretendY = extY;
      if (mode == SYMH || mode == ASYMH)
      {
        sigPretendX = extDimX - extX - 1;
      }
      else // mode == SYMW || mode == ASYMW
      {
        sigPretendX = extDimX - extX;
      }
    }
    else if (direction == TOP)
    {
      sigPretendX = extX;
      if (mode == SYMH || mode == ASYMH)
      {
        sigPretendY = extDimY - extY - 1;
      }
      else // mode == SYMW || mode == ASYMW
      {
        sigPretendY = extDimY - extY;
      }
    }
    else if (direction == RIGHT)
    {
      sigPretendY = extY;
      if (mode == SYMH || mode == ASYMH)
      {
        sigPretendX = sigPretendDimX - extX - 1;
      }
      else
      {
        sigPretendX = sigPretendDimX - extX - 2;
      }
      if (padZero)
      {
        sigPretendX++;
      }
    }
    else if (direction == BOTTOM)
    {
      sigPretendX = extX;
      if (mode == SYMH || mode == ASYMH)
      {
        sigPretendY = sigPretendDimY - extY - 1;
      }
      else
      {
        sigPretendY = sigPretendDimY - extY - 2;
      }
      if (padZero)
      {
        sigPretendY++;
      }
    }

    if (sigPretendX == sigPretendDimX || sigPretendY == sigPretendDimY)
    {
      portalOut.Set(workIndex, 0.0);
    }
    else
    {
      portalOut.Set(workIndex, sym * portalIn.Get(SigPretend2Dto1D(sigPretendX, sigPretendY)));
    }
  }

private:
  const vtkm::Id extDimX, extDimY, sigDimX, sigDimY;
  const vtkm::Id sigStartX, sigStartY, sigPretendDimX, sigPretendDimY;
  const DWTMode mode;
  const ExtensionDirection direction;
  const bool padZero; // treat sigIn as having a column/row zeros
};

// Worklet: perform a simple 2D forward transform
class ForwardTransform2D : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayIn,   // left/top extension
                                WholeArrayIn,   // sigIn
                                WholeArrayIn,   // right/bottom extension
                                WholeArrayIn,   // lowFilter
                                WholeArrayIn,   // highFilter
                                WholeArrayOut); // cA followed by cD
  using ExecutionSignature = void(_1, _2, _3, _4, _5, _6, WorkIndex);
  using InputDomain = _6;

  // Constructor
  ForwardTransform2D(vtkm::Id filter_len,
                     vtkm::Id approx_len,
                     bool odd_low,
                     bool mode_lr,
                     vtkm::Id x1,
                     vtkm::Id y1, // dims of left/top extension
                     vtkm::Id x2,
                     vtkm::Id y2, // dims of signal
                     vtkm::Id startx2,
                     vtkm::Id starty2, // start idx of signal
                     vtkm::Id pretendx2,
                     vtkm::Id pretendy2, // pretend dims of signal
                     vtkm::Id x3,
                     vtkm::Id y3) // dims of right/bottom extension
    : filterLen(filter_len)
    , approxLen(approx_len)
    , outDimX(pretendx2)
    ,
    //outDimY(pretendy2),
    oddlow(odd_low)
    , modeLR(mode_lr)
    , translator(x1, y1, x2, y2, startx2, starty2, pretendx2, pretendy2, x3, y3, mode_lr)
  {
    this->SetStartPosition();
  }

  VTKM_EXEC_CONT
  void Output1Dto2D(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y) const
  {
    x = idx % outDimX;
    y = idx / outDimX;
  }
  VTKM_EXEC_CONT
  vtkm::Id Output2Dto1D(vtkm::Id x, vtkm::Id y) const { return y * outDimX + x; }

// Use 64-bit float for convolution calculation
#define VAL vtkm::Float64
#define MAKEVAL(a) (static_cast<VAL>(a))
  template <typename InPortalType1, typename InPortalType2, typename InPortalType3>
  VTKM_EXEC_CONT VAL GetVal(const InPortalType1& portal1,
                            const InPortalType2& portal2,
                            const InPortalType3& portal3,
                            vtkm::Id inMatrix,
                            vtkm::Id inIdx) const
  {
    if (inMatrix == 1)
    {
      return MAKEVAL(portal1.Get(inIdx));
    }
    else if (inMatrix == 2)
    {
      return MAKEVAL(portal2.Get(inIdx));
    }
    else if (inMatrix == 3)
    {
      return MAKEVAL(portal3.Get(inIdx));
    }
    else
    {
      return -1;
    }
  }

  template <typename InPortalType1,
            typename InPortalType2,
            typename InPortalType3,
            typename FilterPortalType,
            typename OutputPortalType>
  VTKM_EXEC_CONT void operator()(const InPortalType1& inPortal1, // left/top extension
                                 const InPortalType2& inPortal2, // signal
                                 const InPortalType3& inPortal3, // right/bottom extension
                                 const FilterPortalType& lowFilter,
                                 const FilterPortalType& highFilter,
                                 OutputPortalType& coeffOut,
                                 const vtkm::Id& workIndex) const
  {
    vtkm::Id workX, workY, output1D;
    Output1Dto2D(workIndex, workX, workY);
    vtkm::Id inputMatrix = 0, inputIdx = 0;
    using OutputValueType = typename OutputPortalType::ValueType;

    if (modeLR)
    {
      if (workX % 2 == 0) // calculate cA
      {
        vtkm::Id xl = lstart + workX;
        VAL sum = MAKEVAL(0.0);
        for (vtkm::Id k = filterLen - 1; k > -1; k--)
        {
          translator.Translate2Dto1D(xl, workY, inputMatrix, inputIdx);
          sum += lowFilter.Get(k) * GetVal(inPortal1, inPortal2, inPortal3, inputMatrix, inputIdx);
          xl++;
        }
        output1D = Output2Dto1D(workX / 2, workY);
        coeffOut.Set(output1D, static_cast<OutputValueType>(sum));
      }
      else // calculate cD
      {
        vtkm::Id xh = hstart + workX - 1;
        VAL sum = MAKEVAL(0.0);
        for (vtkm::Id k = filterLen - 1; k > -1; k--)
        {
          translator.Translate2Dto1D(xh, workY, inputMatrix, inputIdx);
          sum += highFilter.Get(k) * GetVal(inPortal1, inPortal2, inPortal3, inputMatrix, inputIdx);
          xh++;
        }
        output1D = Output2Dto1D((workX - 1) / 2 + approxLen, workY);
        coeffOut.Set(output1D, static_cast<OutputValueType>(sum));
      }
    }
    else // top-down order
    {
      if (workY % 2 == 0) // calculate cA
      {
        vtkm::Id yl = lstart + workY;
        VAL sum = MAKEVAL(0.0);
        for (vtkm::Id k = filterLen - 1; k > -1; k--)
        {
          translator.Translate2Dto1D(workX, yl, inputMatrix, inputIdx);
          sum += lowFilter.Get(k) * GetVal(inPortal1, inPortal2, inPortal3, inputMatrix, inputIdx);
          yl++;
        }
        output1D = Output2Dto1D(workX, workY / 2);
        coeffOut.Set(output1D, static_cast<OutputValueType>(sum));
      }
      else // calculate cD
      {
        vtkm::Id yh = hstart + workY - 1;
        VAL sum = MAKEVAL(0.0);
        for (vtkm::Id k = filterLen - 1; k > -1; k--)
        {
          translator.Translate2Dto1D(workX, yh, inputMatrix, inputIdx);
          sum += highFilter.Get(k) * GetVal(inPortal1, inPortal2, inPortal3, inputMatrix, inputIdx);
          yh++;
        }
        output1D = Output2Dto1D(workX, (workY - 1) / 2 + approxLen);
        coeffOut.Set(output1D, static_cast<OutputValueType>(sum));
      }
    }
  }
#undef MAKEVAL
#undef VAL

private:
  const vtkm::Id filterLen, approxLen;
  const vtkm::Id outDimX;
  //const vtkm::Id outDimY; Not used
  bool oddlow;
  bool modeLR; // true = left right; false = top down.
  const IndexTranslator3Matrices translator;
  vtkm::Id lstart, hstart;

  VTKM_EXEC_CONT
  void SetStartPosition()
  {
    this->lstart = this->oddlow ? 1 : 0;
    this->hstart = 1;
  }
};

//  ---------------------------------------------------
//  |      |          |      |      |          |      |
//  |      |          |      |      |          |      |
//  | ext1 |    cA    | ext2 | ext3 |    cD    | ext4 |
//  | (x1) |   (xa)   | (x2) | (x3) |   (xd)   | (x4) |
//  |      |          |      |      |          |      |
//  ----------------------------------------------------
//  portal1: ext1
//  portal2: ext2
//  portal3: ext3
//  portal4: ext4
//  portal5: cA + cD
// Worklet: perform a simple 2D inverse transform
class InverseTransform2D : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayIn, // ext1
                                WholeArrayIn, // ext2
                                WholeArrayIn, // ext3
                                WholeArrayIn, // ext4
                                WholeArrayIn, // cA+cD (signal)
                                WholeArrayIn, // lowFilter
                                WholeArrayIn, // highFilter
                                FieldOut);    // outptu coeffs
  using ExecutionSignature = void(_1, _2, _3, _4, _5, _6, _7, _8, WorkIndex);
  using InputDomain = _8;

  // Constructor
  InverseTransform2D(vtkm::Id fil_len,
                     vtkm::Id x_1,
                     vtkm::Id y_1, // ext1
                     vtkm::Id x_a,
                     vtkm::Id y_a, // cA
                     vtkm::Id x_2,
                     vtkm::Id y_2, // ext2
                     vtkm::Id x_3,
                     vtkm::Id y_3, // ext3
                     vtkm::Id x_d,
                     vtkm::Id y_d, // cD
                     vtkm::Id x_4,
                     vtkm::Id y_4, // ext4
                     vtkm::Id x_5,
                     vtkm::Id y_5,
                     vtkm::Id startX5,
                     vtkm::Id startY5,
                     bool mode_lr)
    : filterLen(fil_len)
    , translator(x_1,
                 y_1,
                 x_a,
                 y_a,
                 x_2,
                 y_2,
                 x_3,
                 y_3,
                 x_d,
                 y_d,
                 x_4,
                 y_4,
                 x_5,
                 y_5,
                 startX5,
                 startY5,
                 mode_lr)
    , modeLR(mode_lr)
  {
    if (modeLR)
    {
      outputDimX = x_a + x_d;
      outputDimY = y_1;
      cALenExtended = x_1 + x_a + x_2;
    }
    else
    {
      outputDimX = x_1;
      outputDimY = y_a + y_d;
      cALenExtended = y_1 + y_a + y_2;
    }
  }

  VTKM_EXEC_CONT
  void Output1Dto2D(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y) const
  {
    x = idx % outputDimX;
    y = idx / outputDimX;
  }

// Use 64-bit float for convolution calculation
#define VAL vtkm::Float64
#define MAKEVAL(a) (static_cast<VAL>(a))
  template <typename InPortalType1,
            typename InPortalType2,
            typename InPortalType3,
            typename InPortalType4,
            typename InPortalTypecAcD>
  VTKM_EXEC_CONT VAL GetVal(const InPortalType1& ext1,
                            const InPortalType2& ext2,
                            const InPortalType3& ext3,
                            const InPortalType4& ext4,
                            const InPortalTypecAcD& cAcD,
                            vtkm::Id inMatrix,
                            vtkm::Id inIdx) const
  {
    if (inMatrix == 1)
    {
      return MAKEVAL(ext1.Get(inIdx));
    }
    else if (inMatrix == 2)
    {
      return MAKEVAL(ext2.Get(inIdx));
    }
    else if (inMatrix == 3)
    {
      return MAKEVAL(ext3.Get(inIdx));
    }
    else if (inMatrix == 4)
    {
      return MAKEVAL(ext4.Get(inIdx));
    }
    else if (inMatrix == 5)
    {
      return MAKEVAL(cAcD.Get(inIdx));
    }
    else
    {
      return -1;
    }
  }

  template <typename InPortalType1,
            typename InPortalType2,
            typename InPortalType3,
            typename InPortalType4,
            typename InPortalTypecAcD,
            typename FilterPortalType,
            typename OutputValueType>
  VTKM_EXEC void operator()(const InPortalType1& portal1,
                            const InPortalType2& portal2,
                            const InPortalType3& portal3,
                            const InPortalType4& portal4,
                            const InPortalTypecAcD& portalcAcD,
                            const FilterPortalType& lowFilter,
                            const FilterPortalType& highFilter,
                            OutputValueType& coeffOut,
                            const vtkm::Id& workIdx) const
  {
    vtkm::Id workX, workY;
    vtkm::Id k1 = 0, k2 = 0, xi = 0, yi = 0, inputMatrix = 0, inputIdx = 0;
    Output1Dto2D(workIdx, workX, workY);

    // left-right, odd filter
    if (modeLR && (filterLen % 2 != 0))
    {
      if (workX % 2 != 0)
      {
        k1 = filterLen - 2;
        k2 = filterLen - 1;
      }
      else
      {
        k1 = filterLen - 1;
        k2 = filterLen - 2;
      }

      VAL sum = 0.0;
      xi = (workX + 1) / 2;
      while (k1 > -1)
      {
        translator.Translate2Dto1D(xi, workY, inputMatrix, inputIdx);
        sum += lowFilter.Get(k1) *
          GetVal(portal1, portal2, portal3, portal4, portalcAcD, inputMatrix, inputIdx);
        xi++;
        k1 -= 2;
      }
      xi = workX / 2;
      while (k2 > -1)
      {
        translator.Translate2Dto1D(xi + cALenExtended, workY, inputMatrix, inputIdx);
        sum += highFilter.Get(k2) *
          GetVal(portal1, portal2, portal3, portal4, portalcAcD, inputMatrix, inputIdx);
        xi++;
        k2 -= 2;
      }
      coeffOut = static_cast<OutputValueType>(sum);
    }

    // top-down, odd filter
    else if (!modeLR && (filterLen % 2 != 0))
    {
      if (workY % 2 != 0)
      {
        k1 = filterLen - 2;
        k2 = filterLen - 1;
      }
      else
      {
        k1 = filterLen - 1;
        k2 = filterLen - 2;
      }

      VAL sum = 0.0;
      yi = (workY + 1) / 2;
      while (k1 > -1)
      {
        translator.Translate2Dto1D(workX, yi, inputMatrix, inputIdx);
        VAL cA = GetVal(portal1, portal2, portal3, portal4, portalcAcD, inputMatrix, inputIdx);
        sum += lowFilter.Get(k1) * cA;
        yi++;
        k1 -= 2;
      }
      yi = workY / 2;
      while (k2 > -1)
      {
        translator.Translate2Dto1D(workX, yi + cALenExtended, inputMatrix, inputIdx);
        VAL cD = GetVal(portal1, portal2, portal3, portal4, portalcAcD, inputMatrix, inputIdx);
        sum += highFilter.Get(k2) * cD;
        yi++;
        k2 -= 2;
      }
      coeffOut = static_cast<OutputValueType>(sum);
    }

    // left-right, even filter
    else if (modeLR && (filterLen % 2 == 0))
    {
      if ((filterLen / 2) % 2 != 0) // odd length half filter
      {
        xi = workX / 2;
        if (workX % 2 != 0)
        {
          k1 = filterLen - 1;
        }
        else
        {
          k1 = filterLen - 2;
        }
      }
      else // even length half filter
      {
        xi = (workX + 1) / 2;
        if (workX % 2 != 0)
        {
          k1 = filterLen - 2;
        }
        else
        {
          k1 = filterLen - 1;
        }
      }
      VAL cA, cD;
      VAL sum = 0.0;
      while (k1 > -1)
      {
        translator.Translate2Dto1D(xi, workY, inputMatrix, inputIdx);
        cA = GetVal(portal1, portal2, portal3, portal4, portalcAcD, inputMatrix, inputIdx);
        translator.Translate2Dto1D(xi + cALenExtended, workY, inputMatrix, inputIdx);
        cD = GetVal(portal1, portal2, portal3, portal4, portalcAcD, inputMatrix, inputIdx);
        sum += lowFilter.Get(k1) * cA + highFilter.Get(k1) * cD;
        xi++;
        k1 -= 2;
      }
      coeffOut = static_cast<OutputValueType>(sum);
    }

    // top-down, even filter
    else
    {
      if ((filterLen / 2) % 2 != 0)
      {
        yi = workY / 2;
        if (workY % 2 != 0)
        {
          k1 = filterLen - 1;
        }
        else
        {
          k1 = filterLen - 2;
        }
      }
      else
      {
        yi = (workY + 1) / 2;
        if (workY % 2 != 0)
        {
          k1 = filterLen - 2;
        }
        else
        {
          k1 = filterLen - 1;
        }
      }
      VAL cA, cD;
      VAL sum = 0.0;
      while (k1 > -1)
      {
        translator.Translate2Dto1D(workX, yi, inputMatrix, inputIdx);
        cA = GetVal(portal1, portal2, portal3, portal4, portalcAcD, inputMatrix, inputIdx);
        translator.Translate2Dto1D(workX, yi + cALenExtended, inputMatrix, inputIdx);
        cD = GetVal(portal1, portal2, portal3, portal4, portalcAcD, inputMatrix, inputIdx);
        sum += lowFilter.Get(k1) * cA + highFilter.Get(k1) * cD;
        yi++;
        k1 -= 2;
      }
      coeffOut = static_cast<OutputValueType>(sum);
    }
  }
#undef MAKEVAL
#undef VAL

private:
  const vtkm::Id filterLen;
  vtkm::Id outputDimX, outputDimY;
  vtkm::Id cALenExtended; // Number of cA at the beginning of input, followed by cD
  const IndexTranslator6Matrices translator;
  const bool modeLR;
};

// Worklet: perform a simple 1D forward transform
class ForwardTransform : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayIn,   // sigIn
                                WholeArrayIn,   // lowFilter
                                WholeArrayIn,   // highFilter
                                WholeArrayOut); // cA followed by cD
  using ExecutionSignature = void(_1, _2, _3, _4, WorkIndex);
  using InputDomain = _1;

  // Constructor
  ForwardTransform(vtkm::Id filLen,
                   vtkm::Id approx_len,
                   vtkm::Id detail_len,
                   bool odd_low,
                   bool odd_high)
    : filterLen(filLen)
    , approxLen(approx_len)
    , detailLen(detail_len)
    , oddlow(odd_low)
    , oddhigh(odd_high)
  {
    this->SetStartPosition();
  }

// Use 64-bit float for convolution calculation
#define VAL vtkm::Float64
#define MAKEVAL(a) (static_cast<VAL>(a))
  template <typename InputPortalType, typename FilterPortalType, typename OutputPortalType>
  VTKM_EXEC void operator()(const InputPortalType& signalIn,
                            const FilterPortalType lowFilter,
                            const FilterPortalType highFilter,
                            OutputPortalType& coeffOut,
                            const vtkm::Id& workIndex) const
  {
    using OutputValueType = typename OutputPortalType::ValueType;
    if (workIndex < approxLen + detailLen)
    {
      if (workIndex % 2 == 0) // calculate cA
      {
        vtkm::Id xl = xlstart + workIndex;
        VAL sum = MAKEVAL(0.0);
        for (vtkm::Id k = filterLen - 1; k >= 0; k--)
        {
          sum += lowFilter.Get(k) * MAKEVAL(signalIn.Get(xl++));
        }
        vtkm::Id outputIdx = workIndex / 2; // put cA at the beginning
        coeffOut.Set(outputIdx, static_cast<OutputValueType>(sum));
      }
      else // calculate cD
      {
        VAL sum = MAKEVAL(0.0);
        vtkm::Id xh = xhstart + workIndex - 1;
        for (vtkm::Id k = filterLen - 1; k >= 0; k--)
        {
          sum += highFilter.Get(k) * MAKEVAL(signalIn.Get(xh++));
        }
        vtkm::Id outputIdx = approxLen + (workIndex - 1) / 2; // put cD after cA
        coeffOut.Set(outputIdx, static_cast<OutputValueType>(sum));
      }
    }
  }
#undef MAKEVAL
#undef VAL

private:
  const vtkm::Id filterLen, approxLen, detailLen; // filter and outcome coeff length.
  bool oddlow, oddhigh;
  vtkm::Id xlstart, xhstart;

  VTKM_EXEC_CONT
  void SetStartPosition()
  {
    this->xlstart = this->oddlow ? 1 : 0;
    this->xhstart = this->oddhigh ? 1 : 0;
  }
};

// Worklet: perform an 1D inverse transform for odd length, symmetric filters.
class InverseTransformOdd : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayIn,   // Input: coeffs, cA followed by cD
                                WholeArrayIn,   // lowFilter
                                WholeArrayIn,   // highFilter
                                WholeArrayOut); // output
  using ExecutionSignature = void(_1, _2, _3, _4, WorkIndex);
  using InputDomain = _1;

  // Constructor
  VTKM_EXEC_CONT
  InverseTransformOdd(vtkm::Id filLen, vtkm::Id ca_len, vtkm::Id ext_len)
    : filterLen(filLen)
    //, cALen(ca_len)
    , cALen2(ca_len * 2)
    , cALenExtended(ext_len)
  {
  }

// Use 64-bit float for convolution calculation
#define VAL vtkm::Float64
#define MAKEVAL(a) (static_cast<VAL>(a))
  template <typename InputPortalType, typename FilterPortalType, typename OutputPortalType>
  VTKM_EXEC void operator()(const InputPortalType& coeffs,
                            const FilterPortalType& lowFilter,
                            const FilterPortalType& highFilter,
                            OutputPortalType& sigOut,
                            vtkm::Id workIndex) const
  {
    if (workIndex >= cALen2) // valid calculation region
    {
      return;
    }
    vtkm::Id xi1 = (workIndex + 1) / 2;                     // coeff indices
    vtkm::Id xi2 = this->cALenExtended + ((workIndex) / 2); // coeff indices
    VAL sum = 0.0;

    const bool odd = workIndex % 2 != 0;
    if (odd)
    {
      vtkm::Id k1 = this->filterLen - 2;
      vtkm::Id k2 = this->filterLen - 1;
      for (; k1 >= 0; k1 -= 2, k2 -= 2)
      {
        sum += lowFilter.Get(k1) * MAKEVAL(coeffs.Get(xi1++));
        sum += highFilter.Get(k2) * MAKEVAL(coeffs.Get(xi2++));
      }
      if (k2 >= 0)
      {
        sum += highFilter.Get(k2) * MAKEVAL(coeffs.Get(xi2++));
      }
    }
    else //even
    {
      vtkm::Id k1 = this->filterLen - 1;
      vtkm::Id k2 = this->filterLen - 2;
      for (; k2 >= 0; k1 -= 2, k2 -= 2)
      {
        sum += lowFilter.Get(k1) * MAKEVAL(coeffs.Get(xi1++));
        sum += highFilter.Get(k2) * MAKEVAL(coeffs.Get(xi2++));
      }
      if (k1 >= 0)
      {
        sum += lowFilter.Get(k1) * MAKEVAL(coeffs.Get(xi1++));
      }
    }

    sigOut.Set(workIndex, static_cast<typename OutputPortalType::ValueType>(sum));
  }

#undef MAKEVAL
#undef VAL

private:
  const vtkm::Id filterLen; // filter length.
  //const vtkm::Id cALen;         // Number of actual cAs  (Not used)
  const vtkm::Id cALen2;        //  = cALen * 2
  const vtkm::Id cALenExtended; // Number of cA at the beginning of input, followed by cD
};

// Worklet: perform an 1D inverse transform for even length, symmetric filters.
class InverseTransformEven : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayIn,   // Input: coeffs, cA followed by cD
                                WholeArrayIn,   // lowFilter
                                WholeArrayIn,   // highFilter
                                WholeArrayOut); // output
  using ExecutionSignature = void(_1, _2, _3, _4, WorkIndex);
  using InputDomain = _1;

  // Constructor
  InverseTransformEven(vtkm::Id filtL, vtkm::Id cAL, vtkm::Id cALExt, bool m)
    : filterLen(filtL)
    //, cALen(cAL)
    , cALen2(cAL * 2)
    , cALenExtended(cALExt)
    , matlab(m)
  {
  }

// Use 64-bit float for convolution calculation
#define VAL vtkm::Float64
#define MAKEVAL(a) (static_cast<VAL>(a))
  template <typename InputPortalType, typename FilterPortalType, typename OutputPortalType>
  VTKM_EXEC void operator()(const InputPortalType& coeffs,
                            const FilterPortalType& lowFilter,
                            const FilterPortalType& highFilter,
                            OutputPortalType& sigOut,
                            const vtkm::Id& workIndex) const
  {
    if (workIndex < cALen2) // valid calculation region
    {
      vtkm::Id xi; // coeff indices
      vtkm::Id k;  // indices for low and high filter
      VAL sum = 0.0;

      if (matlab || (filterLen / 2) % 2 != 0) // odd length half filter
      {
        xi = workIndex / 2;
        if (workIndex % 2 != 0)
        {
          k = filterLen - 1;
        }
        else
        {
          k = filterLen - 2;
        }
      }
      else
      {
        xi = (workIndex + 1) / 2;
        if (workIndex % 2 != 0)
        {
          k = filterLen - 2;
        }
        else
        {
          k = filterLen - 1;
        }
      }

      while (k > -1) // k >= 0
      {
        sum += lowFilter.Get(k) * MAKEVAL(coeffs.Get(xi)) +            // cA
          highFilter.Get(k) * MAKEVAL(coeffs.Get(xi + cALenExtended)); // cD
        xi++;
        k -= 2;
      }

      sigOut.Set(workIndex, static_cast<typename OutputPortalType::ValueType>(sum));
    }
  }
#undef MAKEVAL
#undef VAL

private:
  const vtkm::Id filterLen; // filter length.
  //const vtkm::Id cALen;         // Number of actual cAs (not used)
  const vtkm::Id cALen2;        //  = cALen * 2
  const vtkm::Id cALenExtended; // Number of cA at the beginning of input, followed by cD
  bool matlab;                  // followed the naming convention from VAPOR
                                // It's always false for the 1st 4 filters.
};

class ThresholdWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(FieldInOut); // Thresholding in-place
  using ExecutionSignature = void(_1);
  using InputDomain = _1;

  // Constructor
  ThresholdWorklet(vtkm::Float64 t)
    : threshold(t)
    , // must pass in a positive val
    neg_threshold(t * -1.0)
  {
  }

  template <typename ValueType>
  VTKM_EXEC void operator()(ValueType& coeffVal) const
  {
    if (neg_threshold < coeffVal && coeffVal < threshold)
    {
      coeffVal = 0.0;
    }
  }

private:
  vtkm::Float64 threshold;     // positive
  vtkm::Float64 neg_threshold; // negative
};

class SquaredDeviation : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(FieldIn, FieldOut);
  using ExecutionSignature = _2(_1);
  using InputDomain = _1;

  // Constructor
  template <typename ValueType>
  VTKM_EXEC_CONT SquaredDeviation(ValueType t)
  {
    this->mean = static_cast<vtkm::Float64>(t);
  }

  template <typename ValueType>
  VTKM_EXEC ValueType operator()(const ValueType& num) const
  {
    vtkm::Float64 num64 = static_cast<vtkm::Float64>(num);
    vtkm::Float64 diff = this->mean - num64;
    return static_cast<ValueType>(diff * diff);
  }

private:
  vtkm::Float64 mean;
};

class Differencer : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(FieldIn, FieldIn, FieldOut);
  using ExecutionSignature = _3(_1, _2);
  using InputDomain = _1;

  template <typename ValueType1, typename ValueType2>
  VTKM_EXEC ValueType1 operator()(const ValueType1& v1, const ValueType2& v2) const
  {
    return v1 - static_cast<ValueType1>(v2);
  }
};

class SquareWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(FieldIn, FieldOut);
  using ExecutionSignature = _2(_1);
  using InputDomain = _1;

  template <typename ValueType>
  VTKM_EXEC ValueType operator()(const ValueType& v) const
  {
    return (v * v);
  }
};

class CopyWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayIn, WholeArrayOut);
  using ExecutionSignature = void(_1, _2, WorkIndex);
  using InputDomain = _1;

  // Constructor
  VTKM_EXEC_CONT
  CopyWorklet(vtkm::Id idx) { this->startIdx = idx; }

  template <typename PortalInType, typename PortalOutType>
  VTKM_EXEC void operator()(const PortalInType& portalIn,
                            PortalOutType& portalOut,
                            const vtkm::Id& workIndex) const
  {
    portalOut.Set((startIdx + workIndex), portalIn.Get(workIndex));
  }

private:
  vtkm::Id startIdx;
};

// Worklet for 1D signal extension no. 1
class LeftSYMHExtentionWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayOut, // extension part
                                WholeArrayIn); // signal part
  using ExecutionSignature = void(_1, _2, WorkIndex);
  using InputDomain = _1;

  // Constructor
  VTKM_EXEC_CONT
  LeftSYMHExtentionWorklet(vtkm::Id len)
    : addLen(len)
  {
  }

  template <typename PortalOutType, typename PortalInType>
  VTKM_EXEC void operator()(PortalOutType& portalOut,
                            const PortalInType& portalIn,
                            const vtkm::Id& workIndex) const
  {
    portalOut.Set(workIndex, portalIn.Get(this->addLen - workIndex - 1));
  }

private:
  vtkm::Id addLen;
};

// Worklet for 1D signal extension no. 2
class LeftSYMWExtentionWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayOut, // extension part
                                WholeArrayIn); // signal part
  using ExecutionSignature = void(_1, _2, WorkIndex);
  using InputDomain = _1;

  // Constructor
  VTKM_EXEC_CONT
  LeftSYMWExtentionWorklet(vtkm::Id len)
    : addLen(len)
  {
  }

  template <typename PortalOutType, typename PortalInType>
  VTKM_EXEC void operator()(PortalOutType& portalOut,
                            const PortalInType& portalIn,
                            const vtkm::Id& workIndex) const
  {
    portalOut.Set(workIndex, portalIn.Get(this->addLen - workIndex));
  }

private:
  vtkm::Id addLen;
};

// Worklet for 1D signal extension no. 3
class LeftASYMHExtentionWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayOut, // extension part
                                WholeArrayIn); // signal part
  using ExecutionSignature = void(_1, _2, WorkIndex);
  using InputDomain = _1;

  // Constructor
  VTKM_EXEC_CONT
  LeftASYMHExtentionWorklet(vtkm::Id len)
    : addLen(len)
  {
  }

  template <typename PortalOutType, typename PortalInType>
  VTKM_EXEC_CONT void operator()(PortalOutType& portalOut,
                                 const PortalInType& portalIn,
                                 const vtkm::Id& workIndex) const
  {
    portalOut.Set(workIndex, portalIn.Get(addLen - workIndex - 1) * (-1.0));
  }

private:
  vtkm::Id addLen;
};

// Worklet for 1D signal extension no. 4
class LeftASYMWExtentionWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayOut, // extension part
                                WholeArrayIn); // signal part
  using ExecutionSignature = void(_1, _2, WorkIndex);
  using InputDomain = _1;

  // Constructor
  VTKM_EXEC_CONT
  LeftASYMWExtentionWorklet(vtkm::Id len)
    : addLen(len)
  {
  }

  template <typename PortalOutType, typename PortalInType>
  VTKM_EXEC_CONT void operator()(PortalOutType& portalOut,
                                 const PortalInType& portalIn,
                                 const vtkm::Id& workIndex) const
  {
    portalOut.Set(workIndex, portalIn.Get(addLen - workIndex) * (-1.0));
  }

private:
  vtkm::Id addLen;
};

// Worklet for 1D signal extension no. 5
class RightSYMHExtentionWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayOut, // extension part
                                WholeArrayIn); // signal part
  using ExecutionSignature = void(_1, _2, WorkIndex);
  using InputDomain = _1;

  // Constructor
  VTKM_EXEC_CONT
  RightSYMHExtentionWorklet(vtkm::Id sigInl)
    : sigInLen(sigInl)
  {
  }

  template <typename PortalOutType, typename PortalInType>
  VTKM_EXEC void operator()(PortalOutType& portalOut,
                            const PortalInType& portalIn,
                            const vtkm::Id& workIndex) const
  {
    portalOut.Set(workIndex, portalIn.Get(this->sigInLen - workIndex - 1));
  }

private:
  vtkm::Id sigInLen;
};

// Worklet for 1D signal extension no. 6
class RightSYMWExtentionWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayOut, // extension part
                                WholeArrayIn); // signal part
  using ExecutionSignature = void(_1, _2, WorkIndex);
  using InputDomain = _1;

  // Constructor
  VTKM_EXEC_CONT
  RightSYMWExtentionWorklet(vtkm::Id sigInl)
    : sigInLen(sigInl)
  {
  }

  template <typename PortalOutType, typename PortalInType>
  VTKM_EXEC void operator()(PortalOutType& portalOut,
                            const PortalInType& portalIn,
                            const vtkm::Id& workIndex) const
  {
    portalOut.Set(workIndex, portalIn.Get(this->sigInLen - workIndex - 2));
  }

private:
  vtkm::Id sigInLen;
};

// Worklet for 1D signal extension no. 7
class RightASYMHExtentionWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayOut, // extension part
                                WholeArrayIn); // signal part
  using ExecutionSignature = void(_1, _2, WorkIndex);
  using InputDomain = _1;

  // Constructor
  VTKM_EXEC_CONT
  RightASYMHExtentionWorklet(vtkm::Id sigInl)
    : sigInLen(sigInl)
  {
  }

  template <typename PortalOutType, typename PortalInType>
  VTKM_EXEC_CONT void operator()(PortalOutType& portalOut,
                                 const PortalInType& portalIn,
                                 const vtkm::Id& workIndex) const
  {
    portalOut.Set(workIndex, portalIn.Get(sigInLen - workIndex - 1) * (-1.0));
  }

private:
  vtkm::Id sigInLen;
};

// Worklet for 1D signal extension no. 8
class RightASYMWExtentionWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayOut, // extension part
                                WholeArrayIn); // signal part
  using ExecutionSignature = void(_1, _2, WorkIndex);
  using InputDomain = _1;

  // Constructor
  VTKM_EXEC_CONT
  RightASYMWExtentionWorklet(vtkm::Id sigInl)
    : sigInLen(sigInl)
  {
  }

  template <typename PortalOutType, typename PortalInType>
  VTKM_EXEC_CONT void operator()(PortalOutType& portalOut,
                                 const PortalInType& portalIn,
                                 const vtkm::Id& workIndex) const
  {
    portalOut.Set(workIndex, portalIn.Get(sigInLen - workIndex - 2) * (-1.0));
  }

private:
  vtkm::Id sigInLen;
};

// Assign zero to a single index
class AssignZeroWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayInOut);
  using ExecutionSignature = void(_1, WorkIndex);

  // Constructor
  VTKM_EXEC_CONT
  AssignZeroWorklet(vtkm::Id idx)
    : zeroIdx(idx)
  {
  }

  template <typename PortalType>
  VTKM_EXEC void operator()(PortalType& array, const vtkm::Id& workIdx) const
  {
    if (workIdx == this->zeroIdx)
    {
      array.Set(workIdx, static_cast<typename PortalType::ValueType>(0.0));
    }
  }

private:
  vtkm::Id zeroIdx;
};

// Assign zero to a row or a column in a 2D array.
// Change row or column is controlled by negative indices.
class AssignZero2DWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayInOut);
  using ExecutionSignature = void(_1, WorkIndex);

  // Constructor
  VTKM_EXEC_CONT
  AssignZero2DWorklet(vtkm::Id x, vtkm::Id y, vtkm::Id zero_x, vtkm::Id zero_y)
    : dimX(x)
    , dimY(y)
    , zeroX(zero_x)
    , zeroY(zero_y)
  {
    (void)dimY;
  }

  // Index translation helper
  VTKM_EXEC_CONT
  void GetLogicalDim(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y) const
  {
    x = idx % dimX;
    y = idx / dimX;
  }

  template <typename PortalType>
  VTKM_EXEC void operator()(PortalType& array, const vtkm::Id& workIdx) const
  {
    vtkm::Id x, y;
    GetLogicalDim(workIdx, x, y);
    if (zeroY < 0 && x == zeroX) // assign zero to a column
    {
      array.Set(workIdx, static_cast<typename PortalType::ValueType>(0.0));
    }
    else if (zeroX < 0 && y == zeroY) // assign zero to a row
    {
      array.Set(workIdx, static_cast<typename PortalType::ValueType>(0.0));
    }
  }

private:
  vtkm::Id dimX, dimY;
  vtkm::Id zeroX, zeroY; // element at (zeroX, zeroY) will be assigned zero.
                         // each becomes a wild card if negative
};

// Assign zero to a plane (2D) in a 3D cube.
// Which plane to assign zero is controlled by negative indices.
class AssignZero3DWorklet : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(WholeArrayInOut);
  using ExecutionSignature = void(_1, WorkIndex);

  // Constructor
  VTKM_EXEC_CONT
  AssignZero3DWorklet(vtkm::Id x,
                      vtkm::Id y,
                      vtkm::Id z,
                      vtkm::Id zero_x,
                      vtkm::Id zero_y,
                      vtkm::Id zero_z)
    : dimX(x)
    , dimY(y)
    , dimZ(z)
    , zeroX(zero_x)
    , zeroY(zero_y)
    , zeroZ(zero_z)
  {
    (void)dimZ;
  }

  // Index translation helper
  VTKM_EXEC_CONT
  void GetLogicalDim(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y, vtkm::Id& z) const
  {
    z = idx / (dimX * dimY);
    y = (idx - z * dimX * dimY) / dimX;
    x = idx % dimX;
  }

  template <typename PortalType>
  VTKM_EXEC void operator()(PortalType& array, const vtkm::Id& workIdx) const
  {
    vtkm::Id x, y, z;
    GetLogicalDim(workIdx, x, y, z);
    if (zeroZ < 0 && zeroY < 0 && x == zeroX) // plane perpendicular to X axis
    {
      array.Set(workIdx, static_cast<typename PortalType::ValueType>(0.0));
    }
    else if (zeroZ < 0 && zeroX < 0 && y == zeroY) // plane perpendicular to Y axis
    {
      array.Set(workIdx, static_cast<typename PortalType::ValueType>(0.0));
    }
    else if (zeroY < 0 && zeroX < 0 && z == zeroZ) // plane perpendicular to Z axis
    {
      array.Set(workIdx, static_cast<typename PortalType::ValueType>(0.0));
    }
  }

private:
  vtkm::Id dimX, dimY, dimZ;
  vtkm::Id zeroX, zeroY, zeroZ; // element at (zeroX, zeroY, zeroZ) will be assigned zero.
                                // each becomes a wild card if negative
};

// Worklet: Copies a small rectangle to become a part of a big rectangle
class RectangleCopyTo : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(FieldIn,        // Input, small rectangle
                                WholeArrayOut); // Output, big rectangle
  using ExecutionSignature = void(_1, _2, WorkIndex);

  // Constructor
  VTKM_EXEC_CONT
  RectangleCopyTo(vtkm::Id inx,
                  vtkm::Id iny,
                  vtkm::Id outx,
                  vtkm::Id outy,
                  vtkm::Id xStart,
                  vtkm::Id yStart)
    : inXLen(inx)
    , inYLen(iny)
    , outXLen(outx)
    , outYLen(outy)
    , outXStart(xStart)
    , outYStart(yStart)
  {
    (void)outYLen;
    (void)inYLen;
  }

  VTKM_EXEC_CONT
  void GetLogicalDimOfInputRect(const vtkm::Id& idx, vtkm::Id& x, vtkm::Id& y) const
  {
    x = idx % inXLen;
    y = idx / inXLen;
  }

  VTKM_EXEC_CONT
  vtkm::Id Get1DIdxOfOutputRect(vtkm::Id x, vtkm::Id y) const { return y * outXLen + x; }

  template <typename ValueInType, typename PortalOutType>
  VTKM_EXEC void operator()(const ValueInType& valueIn,
                            PortalOutType& arrayOut,
                            const vtkm::Id& workIdx) const
  {
    vtkm::Id xOfIn, yOfIn;
    GetLogicalDimOfInputRect(workIdx, xOfIn, yOfIn);
    vtkm::Id outputIdx = Get1DIdxOfOutputRect(xOfIn + outXStart, yOfIn + outYStart);
    arrayOut.Set(outputIdx, valueIn);
  }

private:
  vtkm::Id inXLen, inYLen;
  vtkm::Id outXLen, outYLen;
  vtkm::Id outXStart, outYStart;
};

// Worklet: Copies a small cube to become a part of a big cube
class CubeCopyTo : public vtkm::worklet::WorkletMapField
{
public:
  using ControlSignature = void(FieldIn,        // Input, small cube
                                WholeArrayOut); // Output, big cube
  using ExecutionSignature = void(_1, _2, WorkIndex);

  // Constructor
  VTKM_EXEC_CONT
  CubeCopyTo(vtkm::Id inx,
             vtkm::Id iny,
             vtkm::Id inz,
             vtkm::Id outx,
             vtkm::Id outy,
             vtkm::Id outz,
             vtkm::Id xStart,
             vtkm::Id yStart,
             vtkm::Id zStart)
    : inDimX(inx)
    , inDimY(iny)
    , inDimZ(inz)
    , outDimX(outx)
    , outDimY(outy)
    , outDimZ(outz)
    , outStartX(xStart)
    , outStartY(yStart)
    , outStartZ(zStart)
  {
    (void)outDimZ;
    (void)inDimZ;
  }

  VTKM_EXEC_CONT
  void GetLogicalDimOfInputCube(vtkm::Id idx, vtkm::Id& x, vtkm::Id& y, vtkm::Id& z) const
  {
    z = idx / (inDimX * inDimY);
    y = (idx - z * inDimX * inDimY) / inDimX;
    x = idx % inDimX;
  }

  VTKM_EXEC_CONT
  vtkm::Id Get1DIdxOfOutputCube(vtkm::Id x, vtkm::Id y, vtkm::Id z) const
  {
    return z * outDimX * outDimY + y * outDimX + x;
  }

  template <typename ValueInType, typename PortalOutType>
  VTKM_EXEC void operator()(const ValueInType& valueIn,
                            PortalOutType& arrayOut,
                            const vtkm::Id& workIdx) const
  {
    vtkm::Id inX, inY, inZ;
    GetLogicalDimOfInputCube(workIdx, inX, inY, inZ);
    vtkm::Id outputIdx = Get1DIdxOfOutputCube(inX + outStartX, inY + outStartY, inZ + outStartZ);
    arrayOut.Set(outputIdx, valueIn);
  }

private:
  const vtkm::Id inDimX, inDimY, inDimZ;          // input small cube
  const vtkm::Id outDimX, outDimY, outDimZ;       // output big cube
  const vtkm::Id outStartX, outStartY, outStartZ; // where to put
};

} // namespace wavelets
} // namespace worlet
} // namespace vtkm

#endif // vtk_m_worklet_Wavelets_h
