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

  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 "vtkKWExtractImageIsosurfaceCells.h"

#include "vtkCellType.h"
#include "vtkCharArray.h"
#include "vtkDoubleArray.h"
#include "vtkFloatArray.h"
#include "vtkImageData.h"
#include "vtkIntArray.h"
#include "vtkLongArray.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkShortArray.h"
#include "vtkUnsignedCharArray.h"
#include "vtkUnsignedIntArray.h"
#include "vtkUnsignedLongArray.h"
#include "vtkUnsignedShortArray.h"
#include "vtkUnstructuredGrid.h"

vtkCxxRevisionMacro(vtkKWExtractImageIsosurfaceCells, "$Revision: 1.15 $");
vtkStandardNewMacro(vtkKWExtractImageIsosurfaceCells);

vtkKWExtractImageIsosurfaceCells::vtkKWExtractImageIsosurfaceCells()
{
  this->NumberOfRequiredInputs = 1;
  this->StartingCell[0] = 0;
  this->StartingCell[1] = 0;
  this->StartingCell[2] = 0;
  this->Isovalue = 0.0;
  this->ArrayComponent = 0;
}

vtkKWExtractImageIsosurfaceCells::~vtkKWExtractImageIsosurfaceCells()
{
}

template <class T>
void vtkKWExtractImageIsosurfaceCellsExecute(
  vtkKWExtractImageIsosurfaceCells *self, vtkImageData *input, T *inPtr,
  vtkUnstructuredGrid *output, int dim[3], float isovalue, int startCell[3])
{
  T *visitedCells = new T[dim[0] * dim[1] * dim[2]];

  if ( !visitedCells )
    {
    // not enough memory
    return;
    }

  int progressGuess = dim[0]*dim[1]*dim[2] / 10;
  int processedCells = 0;

  output->Reset();
  output->Allocate(1000,1000);

  vtkPoints *pts = vtkPoints::New();
  int numPoints = 0;

  vtkDataArray *sptr=0;
  T *dptr;
  T isoval = static_cast<T>(isovalue);

  double range[2];
  input->GetScalarRange(range);
  
  isoval = (isovalue < range[0]) ? static_cast<T>(range[0]) : (isoval);
  isoval = (isovalue > range[1]) ? static_cast<T>(range[1]) : (isoval);
  
  switch (input->GetScalarType())
    {
    case VTK_CHAR:
      sptr = vtkCharArray::New();
      break;
    case VTK_UNSIGNED_CHAR:
      sptr = vtkUnsignedCharArray::New();
      break;
    case VTK_SHORT:
      sptr = vtkShortArray::New();
      break;
    case VTK_UNSIGNED_SHORT:
      sptr = vtkUnsignedShortArray::New();
      break;
    case VTK_INT:
      sptr = vtkIntArray::New();
      break;
    case VTK_UNSIGNED_INT:
      sptr = vtkUnsignedIntArray::New();
      break;
    case VTK_LONG:
      sptr = vtkLongArray::New();
      break;
    case VTK_UNSIGNED_LONG:
      sptr = vtkUnsignedLongArray::New();
      break;
    case VTK_FLOAT:
      sptr = vtkFloatArray::New();
      break;
    case VTK_DOUBLE:
      sptr = vtkDoubleArray::New();
      break;
    }
 
  int checkListSize    = 0;
  int checkListMaxSize = 10000;
  int *checkList       = new int[3*checkListMaxSize];

  int i,j,k;

  for ( i = 0; i < dim[0]*dim[1]*dim[2]; i++ )
    {
    visitedCells[i] = 0;
    }

  int above, below;
  int numComps = input->GetNumberOfScalarComponents();
  int comp = self->GetArrayComponent();

  above = 0;
  below = 0;

  int offset[8];
  offset[0] = 0;
  offset[1] = 1*numComps;
  offset[2] = dim[0]*numComps;
  offset[3] = (dim[0] + 1)*numComps;
  offset[4] = dim[0]*dim[1]*numComps;
  offset[5] = (dim[0]*dim[1] + 1)*numComps;
  offset[6] = (dim[0]*dim[1] + dim[0])*numComps;
  offset[7] = (dim[0]*dim[1] + dim[0] + 1)*numComps;

  double spacing[3];
  double origin[3];

  input->GetSpacing( spacing );
  input->GetOrigin( origin );

  checkList[0] = startCell[0];
  checkList[1] = startCell[1];
  checkList[2] = startCell[2];

  checkListSize = 1;
  
  vtkIdType cellPoints[8];

  visitedCells[checkList[2]*dim[0]*dim[1] + checkList[1]*dim[0] + checkList[0]] = 1;

  while ( checkListSize > 0 )
    {
    int voxel[3];
    
    checkListSize--;
    
    voxel[0] = checkList[3*checkListSize  ];
    voxel[1] = checkList[3*checkListSize+1];
    voxel[2] = checkList[3*checkListSize+2];
    
    dptr =
      inPtr + (voxel[2]*dim[0]*dim[1] + voxel[1]*dim[0] + voxel[0])*numComps +
      comp;
    
    below = 0;
    for ( i = 0; i < 8; i++ )
      {
      if ( *(dptr + offset[i]) <= isoval )
        {
        below = 1;
        break;
        }
      }
    
    above = 0;
    for ( i = 0; i < 8; i++ )
      {
      if ( *(dptr + offset[i]) >= isoval )
        {
        above = 1;
        break;
        }
      }
    
    if ( above && below )
      {
      pts->InsertNextPoint(  voxel[0]   *spacing[0] + origin[0],
                             voxel[1]   *spacing[1] + origin[1],
                             voxel[2]   *spacing[2] + origin[2] );
      pts->InsertNextPoint( (voxel[0]+1)*spacing[0] + origin[0],
                            voxel[1]   *spacing[1] + origin[1],
                            voxel[2]   *spacing[2] + origin[2] );
      pts->InsertNextPoint(  voxel[0]   *spacing[0] + origin[0],
                             (voxel[1]+1)*spacing[1] + origin[1],
                             voxel[2]   *spacing[2] + origin[2] );
      pts->InsertNextPoint( (voxel[0]+1)*spacing[0] + origin[0],
                            (voxel[1]+1)*spacing[1] + origin[1],
                            voxel[2]   *spacing[2] + origin[2] );
      pts->InsertNextPoint(  voxel[0]   *spacing[0] + origin[0],
                             voxel[1]   *spacing[1] + origin[1],
                             (voxel[2]+1)*spacing[2] + origin[2] );
      pts->InsertNextPoint( (voxel[0]+1)*spacing[0] + origin[0],
                            voxel[1]   *spacing[1] + origin[1],
                            (voxel[2]+1)*spacing[2] + origin[2] );
      pts->InsertNextPoint(  voxel[0]   *spacing[0] + origin[0],
                             (voxel[1]+1)*spacing[1] + origin[1],
                             (voxel[2]+1)*spacing[2] + origin[2] );
      pts->InsertNextPoint( (voxel[0]+1)*spacing[0] + origin[0],
                            (voxel[1]+1)*spacing[1] + origin[1],
                            (voxel[2]+1)*spacing[2] + origin[2] );
      
      for ( i = 0; i < 8; i++ )
        {
        sptr->InsertNextTuple1( *(dptr + offset[i]) );
        cellPoints[i] = numPoints + i;
        }
      
      numPoints += 8;
      
      output->InsertNextCell( VTK_VOXEL, 8, cellPoints );
      
      for ( k = -1; k <= 1; k++ )
        {
        for ( j = -1; j <= 1; j++ )
          {
          for ( i = -1; i <= 1; i++ )
            {
            int newVoxel[3];
            
            newVoxel[0] = voxel[0] + i;
            newVoxel[1] = voxel[1] + j;
            newVoxel[2] = voxel[2] + k;
            
            if ( newVoxel[0] >= 0 && newVoxel[0] < dim[0]-1 &&
                 newVoxel[1] >= 0 && newVoxel[1] < dim[1]-1 &&
                 newVoxel[2] >= 0 && newVoxel[2] < dim[2]-1 &&
                 visitedCells[ newVoxel[2]*dim[0]*dim[1] +
                             newVoxel[1]*dim[0] + newVoxel[0]] == 0 )
              {
              if (checkListSize >= checkListMaxSize  )
                {
                int *newList = new int[3*4*checkListMaxSize];
                memcpy( newList, checkList, 
                        3*checkListMaxSize*sizeof(int) );
                delete [] checkList;
                checkList = newList;
                checkListMaxSize *= 4;
                }
              visitedCells[newVoxel[2]*dim[0]*dim[1]+
                          newVoxel[1]*dim[0]+newVoxel[0]] = 1;
              checkList[checkListSize*3  ] = newVoxel[0];
              checkList[checkListSize*3+1] = newVoxel[1];
              checkList[checkListSize*3+2] = newVoxel[2];
              checkListSize++;
              }
            }
          }
        }
      }
    processedCells++;
    
    if ( processedCells % 10000 == 0 )
      {
      float p = static_cast<float>( processedCells ) / 
        static_cast<float>( progressGuess );
      if ( p > 0.95 )
        {
        p = 0.95;
        }
      self->UpdateProgress( p );
      }
    }
  
  output->SetPoints( pts );
  output->GetPointData()->SetScalars( sptr );

  sptr->Delete();
  pts->Delete();

  delete [] checkList;
  delete [] visitedCells;

  output->Squeeze();
}

void vtkKWExtractImageIsosurfaceCells::ExecuteData(vtkDataObject *vtkNotUsed(dataObject))
{
  vtkImageData *input = this->GetInput();
  vtkUnstructuredGrid *output = this->GetOutput();

  this->UpdateProgress( 0.0 );

  if ( !input )
    {
    vtkErrorMacro("No input");
    return;
    }

  input->RequestExactExtentOff();
  
  int dim[3];
  input->GetDimensions(dim);

  if ( dim[0] < 2 || dim[1] < 2 || dim[2] < 2 )
    {
    vtkErrorMacro("Data is not a volume");
    return;
    }

  if ( this->StartingCell[0] < 0 || this->StartingCell[0] >= dim[0] - 1 ||
       this->StartingCell[1] < 0 || this->StartingCell[1] >= dim[1] - 1 ||
       this->StartingCell[2] < 0 || this->StartingCell[2] >= dim[2] - 1 )
    {
    vtkErrorMacro("Starting cell not in volume");
    return;
    }

  void *inPtr = input->GetScalarPointer();
  
  switch (input->GetScalarType())
    {
    vtkTemplateMacro(vtkKWExtractImageIsosurfaceCellsExecute(this,
                      input, (VTK_TT *)(inPtr), output, dim, this->Isovalue,
                      this->StartingCell));
    default:
      vtkErrorMacro("Unknown scalar type");
      return;
    }  
}

void vtkKWExtractImageIsosurfaceCells::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
 
  os << indent << "StartingCell: (" << this->StartingCell[0] << ", "
     << this->StartingCell[1] << ", " << this->StartingCell[2] << ")" << endl;
  os << indent << "Isovalue: " << this->Isovalue << endl;
  os << indent << "ArrayComponent: " << this->ArrayComponent << endl;
}

