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

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

#include "vtkLinearExtrusionFilter.h"
#include "vtkPolyDataToImageStencil.h"
#include "vtkImageStencilData.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPolyData.h"
#include "vtkCell.h"
#include "vtkImageData.h"
#include "vtkImageStencil.h"
#include "vtkImageStencilData.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkMatrixToLinearTransform.h"
#include "vtkMatrix4x4.h"
#include "vtkTransformPolyDataFilter.h"
#include "vtkPlanarPolyDataToImageStencil.h"
#include "vtkSmartPointer.h"


vtkCxxRevisionMacro(vtkContourToImageStencil, "$Revision: 1.8 $");
vtkStandardNewMacro(vtkContourToImageStencil);

//----------------------------------------------------------------------------
vtkContourToImageStencil::vtkContourToImageStencil()
{
}

//----------------------------------------------------------------------------
vtkContourToImageStencil::~vtkContourToImageStencil()
{
}

//----------------------------------------------------------------------------
void vtkContourToImageStencil::SetInput(vtkPolyData *input)
{
  if (input)
    {
    this->SetInputConnection(0, input->GetProducerPort());
    }
  else
    {
    this->SetInputConnection(0, 0);
    }
}

//----------------------------------------------------------------------------
vtkPolyData *vtkContourToImageStencil::GetInput()
{
  if (this->GetNumberOfInputConnections(0) < 1)
    {
    return NULL;
    }
  
  return vtkPolyData::SafeDownCast(
    this->GetExecutive()->GetInputData(0, 0));
}

//----------------------------------------------------------------------------
// 1. The vtkPlanarPolyDataToImageStencil
//    rasterizes this contour along the XY plane. The vtkImageStencil gives us 
//    the resulting stencil mask (that contains tuples defining the extents of 
//    the contour along each scan line).
// 2. Depending on whether the contour points are arranged clockwise or anti-
//    clockwise, the vtkPlanarPolyDataToImageStencil will give us the inverse region
//    or the actual region within the contour. The FlipStencil() function 
//    determines this by checking if the extent boundaries are labelled 1 or 0.
// 3. If we do need to flip, do that.. fill the output with the complementary
//    extents. 
//    
int vtkContourToImageStencil::RequestData(
  vtkInformation *request,
  vtkInformationVector **inputVector,
  vtkInformationVector *outputVector)
{
  this->Superclass::RequestData(request, inputVector, outputVector);

  vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation *outInfo = outputVector->GetInformationObject(0);

  vtkPolyData *polydata = vtkPolyData::SafeDownCast(
    inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkImageStencilData *output = vtkImageStencilData::SafeDownCast(
    outInfo->Get(vtkDataObject::DATA_OBJECT()));

  // Invalid polydata then return
  if (polydata->GetNumberOfPoints() < 3 || polydata->GetNumberOfCells() != 1)
    {
    return 1;
    }

  double contourBounds[6];
  polydata->GetCell(0)->GetBounds( contourBounds );
  
  int extent[6];
  // If the contour lies on a plane that is in between two planes, (a 
  // partial volume region), assume that the contour lies on the 
  // closest of the two planes. Here we force extent[4] = extent[5] ie the 
  // contour is one slice thick.
  extent[4] = (int)(((
     (contourBounds[4] + contourBounds[5])/2 - this->Origin[2])/this->Spacing[2]) + 0.5); // floor
  extent[5] = (int)(((
     (contourBounds[4] + contourBounds[5])/2 - this->Origin[2])/this->Spacing[2]) + 0.5);
  
  for (unsigned int i=0; i < 2; i++ )
    {
    // In case the contour lies in a partial volume region consider the region
    // that encloses the contour.
    extent[2*i] = (int)((contourBounds[2*i] - this->Origin[i])/this->Spacing[i]); // floor
    extent[2*i+1] = (int)(ceil((contourBounds[2*i+1] - this->Origin[i])/this->Spacing[i]));
    }


  // cout << "Polydata is being rasterized..." << endl;
  // Rasterize the polydata (sweep it along the plane the contour lies on, 
  // bounded by the extrusion) and get extents into a stencil
  vtkSmartPointer< vtkPlanarPolyDataToImageStencil > contourStencilFilter 
                = vtkSmartPointer< vtkPlanarPolyDataToImageStencil >::New();
  contourStencilFilter->SetInput( polydata );
  contourStencilFilter->SetOutputSpacing( this->GetSpacing() );
  contourStencilFilter->SetOutputOrigin( this->GetOrigin() );
  contourStencilFilter->SetOutputWholeExtent(extent);
  contourStencilFilter->Update();
  
  vtkImageStencilData *stencilData = contourStencilFilter->GetOutput();

  // Was the stencil be flipped. The flag `flipStencil' is 1 if the stencil
  // was flipped.  
  int flipStencil = this->FlipStencil( extent, stencilData );

  if (flipStencil == -1) 
    {
    // Something failed
    vtkErrorMacro( << "vtkContourToImageStencil failed" );
    return 1;
    }
  else if (flipStencil == 0)
    {
    output->ShallowCopy( stencilData ); 
    }
  else if (flipStencil == 1)
    {
    output->AllocateExtents();
    int iter = -1;

    // flip the stencil.. do it manually instead of using 
    // a vtkImageStencilDataFlip, since we want to reuse the same output data
    // object here instead of having to do a deepcopy later.
    for (int y=extent[2]; y <= extent[3]; y++, iter = -1)
      {
      int r1,r2;
      int moreSubExtents = 1;
      while( moreSubExtents )
        {
        moreSubExtents = stencilData->GetNextExtent( r1, r2, extent[0], extent[1], y, extent[4], iter);

        // sanity check
        if (r1 <= r2 ) 
          { 
          output->InsertNextExtent( r1, r2, y, extent[4] );
          }
        } // end for each extent tuple
      } // end for each scan line 
    output->SetSpacing(stencilData->GetSpacing());
    output->SetOrigin(stencilData->GetOrigin());
    }

  
  return 1;
}

//----------------------------------------------------------------------------
// Determine whether to flip the stencil or not... If the contour is drawn 
// anticlockwise, the inverse region is considered on by the stencil. 
// For clockwise the inside region is considered on by the stencil. Here
// we will visit 10 scan lines and see if the scan lines have the contour
// bounding box as a part of the contour. If yes, we will flip and take 
// the inverse region.
//  1 1 1 1 1 1 1 1 1 1 1 1              0 0 0 0 0 0 0 0 0 0 0 0 
//  1 1 1 0 0 1 1 1 1 1 1 1              0 0 0 1 1 0 0 0 0 0 0 0
//  1 1 0 0 0 0 1 1 1 1 1 1              0 0 1 1 1 1 0 0 0 0 0 0
//  1 1 0 0 0 0 0 1 1 1 1 1              0 0 1 1 1 1 1 0 0 0 0 0
//  1 1 1 0 0 0 1 1 1 1 1 1              0 0 0 1 1 1 0 0 0 0 0 0
//  1 1 1 1 0 0 0 1 1 1 1 1              0 0 0 0 1 1 1 0 0 0 0 0
//  1 1 1 1 1 0 0 1 1 1 1 1              0 0 0 0 0 1 1 0 0 0 0 0
//  1 1 1 1 1 1 1 1 1 1 1 1              0 0 0 0 0 0 0 0 0 0 0 0
//  
//  Args: 
//  extent - extent of the contour. Where region/inverse region is evaluated
//  stencilData - stencil data passed in 
//  Returns: -1 on failure. 
//           1 if there is a need to flip
//           0 if there is no need to flip
//
int vtkContourToImageStencil::FlipStencil( int extent[6], 
                              vtkImageStencilData *stencilData)
{
  if (extent[4] != extent[5])
    {
    return -1; // Stencil thicker than 1 slice
    }
  
  int iter = 0;
  int numberOfScanLines = 10;
  int idx=0;
  int yWidth = extent[3] - extent[2] + 1;
  
  // If there are fewer than 10 scanlines, use the number available
  if (yWidth < numberOfScanLines)
    {
    numberOfScanLines = yWidth;
    }

  // This array holds the scan lines we will sample.
  int *scanLineIndicesY = new int[numberOfScanLines];
  scanLineIndicesY[0] = extent[2];
  scanLineIndicesY[numberOfScanLines-1] = extent[3];
  int inc = static_cast< int >( yWidth/numberOfScanLines );
  int i;
  for (i=extent[2]+inc; i<extent[3]; i+=inc )
    {
    if (idx > (numberOfScanLines-2)) 
      {
      break;
      }
    scanLineIndicesY[++idx] = i;
    }

  // Determine if each scan line has its stencil mask touching the boundary. If 
  // all numberOfScanLines of them do, the natural thing to do is to flip, else
  // don't flip the stencil.
  
  unsigned int flipScore = 0;
  for (i = 0; i < numberOfScanLines; i++) 
    {
    int r1,r2;
    int moreSubExtents = 1;
    bool leftExtentIsInside = false;
    bool rightExtentIsInside = false;

    // Get all the extent tuples for this scan line
    while( moreSubExtents )
      {
      moreSubExtents = stencilData->GetNextExtent( 
          r1, r2, extent[0], extent[1], scanLineIndicesY[i], extent[4], iter);
      if (r1 == extent[0])
        {
        leftExtentIsInside = true;
        }
      if (r2 == extent[1])
        {
        rightExtentIsInside = true;
        }
      }
    
    iter = 0;
    
    // If the left boundary and the right boundary are 1, increase the 
    // "flip score". If the flip score is more than half, we flip.
    if (leftExtentIsInside && rightExtentIsInside)
      {
      ++flipScore;
      }
    }
 
  delete [] scanLineIndicesY;
  
  // If more than half the lines touched the extent boundaries..
  if (flipScore > (static_cast<unsigned int>( numberOfScanLines ))/2) 
    {
    return 1; 
    }
  
  return 0;
}

//----------------------------------------------------------------------------
// Extent of the output stencil will be the same as the extent of the contour
// 
int vtkContourToImageStencil::RequestInformation(
  vtkInformation *,
  vtkInformationVector **,
  vtkInformationVector *outputVector)
{
  vtkInformation *outInfo = outputVector->GetInformationObject(0);
  
  double contourBounds[6];
  this->GetInput()->GetCell(0)->GetBounds( contourBounds );
  
  int extent[6];
  // If the contour lies on a plane that is in between two planes, (a 
  // partial volume region), assume that the contour lies on the 
  // closest of the two planes. Assume
  extent[4] = (int)(((
     (contourBounds[4] + contourBounds[5])/2 - this->Origin[2])/this->Spacing[2]) + 0.5); // floor
  extent[5] = (int)(((
     (contourBounds[4] + contourBounds[5])/2 - this->Origin[2])/this->Spacing[2]) + 0.5);
  
  for (unsigned int i=0; i < 2; i++ )
    {
    // In case the contour lies in a partial volume region consider the region
    // that encloses the contour.
    extent[2*i] = (int)((contourBounds[2*i] - this->Origin[i])/this->Spacing[i]); // floor
    extent[2*i+1] = (int)(ceil((contourBounds[2*i+1] - this->Origin[i])/this->Spacing[i]));
    }

  outInfo->Set(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), extent, 6);
  this->GetOutput()->SetSpacing( this->GetSpacing() );
  this->GetOutput()->SetOrigin(  this->GetOrigin() );
  return 1;
}

//----------------------------------------------------------------------------
// Ensure that the input is a vtkPolyData.
//
int vtkContourToImageStencil::FillInputPortInformation(int,
                                                        vtkInformation* info)
{
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData");
  return 1;
}

//----------------------------------------------------------------------------
void vtkContourToImageStencil::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  
  os << indent << "Spacing: (" 
     << this->Spacing[0] << ", "
     << this->Spacing[1] << ", "
     << this->Spacing[2] << ")" << std::endl;
  os << indent << "Origin: (" 
     << this->Origin[0] << ", "
     << this->Origin[1] << ", "
     << this->Origin[2] << ")" << std::endl;
}

