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

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.htm for details.

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

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

#include "vtkImageData.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkBitArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkStreamingDemandDrivenPipeline.h"

vtkCxxRevisionMacro(vtkKWOrientationFilter, "$Revision: 1.14 $");
vtkStandardNewMacro(vtkKWOrientationFilter);

//----------------------------------------------------------------------------
void vtkKWOrientationFilter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  
  int idx;
  
  os << indent << "OutputAxes: (" << this->OutputAxes[0];
  for (idx = 1; idx < 3; ++idx)
    {
    os << ", " << this->OutputAxes[idx];
    }
  os << ")\n";  
}

//----------------------------------------------------------------------------
vtkKWOrientationFilter::vtkKWOrientationFilter()
{
  this->OutputAxes[0] = 0;
  this->OutputAxes[1] = 1;
  this->OutputAxes[2] = 2;
}

//----------------------------------------------------------------------------
int vtkKWOrientationFilter::RequestInformation(
  vtkInformation *vtkNotUsed(request),
  vtkInformationVector **inputVector,
  vtkInformationVector *outputVector)
{
  vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation *outInfo = outputVector->GetInformationObject(0);

  // adjust the spacing, etc
  int wExt[6];
  inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), wExt);

  int oExt[6];

  double old_spacing[3];
  inInfo->Get(vtkDataObject::SPACING(), old_spacing);
  float spacing[3];
  
  int i;
  for (i = 0; i < 3; ++i)
    {
    oExt[(this->OutputAxes[i]%3)*2] = wExt[i*2];
    oExt[(this->OutputAxes[i]%3)*2+1] = wExt[i*2+1];
    spacing[this->OutputAxes[i]%3] = old_spacing[i];
    }

  outInfo->Set(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), oExt, 6);

  return 1;
}

//----------------------------------------------------------------------------
// This method computes the input extent necessary to generate the output.
void vtkKWOrientationFilter::ComputeInputUpdateExtent(int inExt[6],
                                                      int outExt[6])
{
  int i;
  
  for (i = 0; i < 3; ++i)
    {
    inExt[i*2] = outExt[(this->OutputAxes[i]%3)*2];
    inExt[i*2+1] = outExt[(this->OutputAxes[i]%3)*2+1];
    }
}

//----------------------------------------------------------------------------
unsigned long vtkKWOrientationFilterGetIndex(unsigned long srcIdx, 
                                             int dim[3], 
                                             int newDim[3], int oaxes[3])
{
  int result[3];
  int src[3];
  
  src[0] = srcIdx%dim[0];
  src[1] = (srcIdx/dim[0])%dim[1];
  src[2] = srcIdx/(dim[0]*dim[1]);
  
  int i;
  int axis;
  for (i = 0; i < 3; ++i)
    {
    axis = oaxes[i]%3;
    result[axis] = src[i];
    if (oaxes[i] > 2)
      {
      result[axis] = newDim[axis] - result[axis] - 1;
      }
    }
  
  // now we have the result in terms of the new ijk
  // so we need to compute the index
  return (result[0] + newDim[0]*(result[1] + result[2]*newDim[1]));
}

//----------------------------------------------------------------------------
template <class T>
void vtkKWOrientationFilterOrient(vtkKWOrientationFilter *self,
                                  vtkImageData *idata, vtkImageData *odata, T *)
{
  // need an array of bits to keep track of what has been written
  vtkBitArray *written = vtkBitArray::New();
  // get the data
  T *data = (T *)idata->GetScalarPointer();
  T *inPtr = data;
  T tempVals1[4];
  T tempVals2[4];
  int numComponents = idata->GetNumberOfScalarComponents();

  int *dim = idata->GetDimensions();
  int *newDim = odata->GetDimensions();
  
  // alocate the array of written voxels and init it to zero
  memset(written->WritePointer(0,dim[0]*dim[1]*dim[2]),0,
         (dim[0]*dim[1]*dim[2]+7)/8);
  
  int *oaxes = self->GetOutputAxes();
  unsigned long srcIdxYZ;
  
  // now loop through the volume, we know it is contiguous
  int x,y,z;
  unsigned long srcIdx, destIdx;
  for (z = 0; z < dim[2]; ++z)
    {
    self->UpdateProgress((float)z/dim[2]);
    for (y = 0; y < dim[1]; ++y)
      {
      srcIdxYZ = (y + z*dim[1])*dim[0];
      for (x = 0; x < dim[0]; ++x)
        {
        srcIdx = srcIdxYZ + x;
        destIdx = vtkKWOrientationFilterGetIndex(srcIdx,dim,newDim,oaxes);
        // store the src pixel in the temp value
        memcpy(tempVals2, inPtr+srcIdx*numComponents,
               sizeof(T)*numComponents);
        // while the dest voxel has not been written to
        while (!written->GetValue(destIdx))
          {
          // store the dest pixel in the temp value
          memcpy(tempVals1, inPtr+destIdx*numComponents,
                 sizeof(T)*numComponents);
          // write the tempval2 to the dest pixel
          memcpy(inPtr+destIdx*numComponents,tempVals2,
                 sizeof(T)*numComponents);
          // move the tempval1 to tempval2
          memcpy(tempVals2, tempVals1, sizeof(T)*numComponents);          
          // mark the dest pixel as written to
          if (destIdx > (unsigned long)written->GetSize())
            {
            vtkGenericWarningMacro(
              "a catastrophic error occurred in the orientation filter");
            }
          written->SetValue(destIdx,1);
          // set the new srcIdx to be the destIdx
          srcIdx = destIdx;
          destIdx = vtkKWOrientationFilterGetIndex(srcIdx,dim,newDim,oaxes);
          }
        }
      }
    }

  written->Delete();
}

//----------------------------------------------------------------------------
int vtkKWOrientationFilter::RequestData(
  vtkInformation* request,
  vtkInformationVector** inputVector,
  vtkInformationVector* outputVector)
{
  vtkInformation *outInfo = outputVector->GetInformationObject(0);
  vtkImageData *output = 
    vtkImageData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
  vtkImageData *input = 
    vtkImageData::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
  /*
    //output->SetExtent(output->GetWholeExtent());
  int wExt[6];
  outInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT());
  outInfo->Set(vtkDataObject::DATA_EXTENT(), wExt, 6);
  */

  // let superclass allocate data
  this->Superclass::RequestData(request, inputVector, outputVector);

  // with 012 there is nothing to do so return
  if (this->OutputAxes[0] == 0 &&
      this->OutputAxes[1] == 1 &&
      this->OutputAxes[2] == 2)
    {
    return 1;
    }
  
  switch (output->GetScalarType())
    {
    vtkTemplateMacro(vtkKWOrientationFilterOrient(this, input, output, (VTK_TT *)0));
    default:
      vtkErrorMacro(<< "Execute: Unknown ScalarType");
      return 1;
    }

  return 1;
}

//----------------------------------------------------------------------------
int vtkKWOrientationFilter::RequestUpdateExtent(
  vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector,
  vtkInformationVector* outputVector)
{
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  int outExt[6];
  outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), outExt);

  int inExt[6];
  this->ComputeInputUpdateExtent(inExt, outExt);
  
  inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), inExt, 6);

  return 1;
}

