//=========================================================================
//  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.
//=========================================================================
#include "vtkClipPolygons.h"

#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkExecutive.h"
#include "vtkFloatArray.h"
#include "vtkGenericCell.h"
#include "vtkImplicitFunction.h"
#include "vtkIncrementalPointLocator.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkLine.h"
#include "vtkMergePoints.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkTransform.h"
#include "vtkTriangle.h"

#include <math.h>

vtkStandardNewMacro(vtkClipPolygons);
vtkCxxSetObjectMacro(vtkClipPolygons, Transform, vtkTransform);

// Construct with user-specified implicit function; InsideOut turned off; value
// set to 0.0; and generate clip scalars turned off.
vtkClipPolygons::vtkClipPolygons()
{
  this->Locator = NULL;
  this->Value = 0.0;
  this->Transform = 0;
  this->ActiveGroupIdx = -1;
  this->IsProcessing = false;

  this->SetNumberOfOutputPorts(2);

  this->GenerateClippedOutput = 0;
  vtkPolyData* output2 = vtkPolyData::New();
  this->GetExecutive()->SetOutputData(1, output2);
  output2->Delete();
}

vtkClipPolygons::~vtkClipPolygons()
{
  if (this->Locator)
  {
    this->Locator->UnRegister(this);
    this->Locator = NULL;
  }
  this->SetTransform(static_cast<vtkTransform*>(0));
  this->RemoveAllClipPolygons();
}

int vtkClipPolygons::IsActiveGroupValid()
{
  if (this->ActiveGroupIdx < 0)
  {
    return 0;
  }
  return 1;
}

void vtkClipPolygons::SetGroupInvert(int val)
{
  if (this->IsActiveGroupValid() &&
    (this->GroupInvert.find(this->ActiveGroupIdx) == this->GroupInvert.end() ||
        this->GroupInvert[this->ActiveGroupIdx] != val))
  {
    this->GroupInvert[this->ActiveGroupIdx] = val;
    this->Modified();
  }
}

void vtkClipPolygons::AddClipPolygon(vtkImplicitFunction* cf)
{
  if (this->IsActiveGroupValid())
  {
    PolygonInfo* cfInfo = new PolygonInfo();
    cfInfo->Polygon = cf;
    this->Polygons[this->ActiveGroupIdx].push_back(cfInfo);
    if (this->Polygons[this->ActiveGroupIdx].size() == 1)
    {
      this->GroupInvert[this->ActiveGroupIdx] = 0;
    }
    this->Modified();
  }
}

void vtkClipPolygons::RemoveClipPolygon(vtkImplicitFunction* cf)
{
  if (cf && this->IsActiveGroupValid())
  {
    bool removed = false;
    for (std::vector<PolygonInfo*>::iterator itRemove =
           this->Polygons[this->ActiveGroupIdx].begin();
         itRemove != this->Polygons[this->ActiveGroupIdx].end(); itRemove++)
    {
      if ((*itRemove)->Polygon == cf)
      {
        delete *itRemove;
        this->Polygons[this->ActiveGroupIdx].erase(itRemove);
        removed = true;
        break;
      }
    }
    if (removed)
    {
      this->Modified();
    }
  }
}

void vtkClipPolygons::RemoveAllClipPolygons()
{
  for (std::map<int, std::vector<PolygonInfo*> >::iterator it = this->Polygons.begin();
       it != this->Polygons.end(); it++)
  {
    for (std::vector<PolygonInfo*>::iterator itRemove = it->second.begin();
         itRemove != it->second.end(); itRemove++)
    {
      delete *itRemove;
    }
  }
  this->Polygons.clear();
  this->Modified();
}

void vtkClipPolygons::SetClipApplyPolygon(int idx, int val)
{
  if (this->IsActiveGroupValid())
  {
    if (idx >= 0 && idx < static_cast<int>(this->Polygons[this->ActiveGroupIdx].size()))
    {
      if (this->Polygons[this->ActiveGroupIdx][idx]->ApplyPolygon != val)
      {
        this->Polygons[this->ActiveGroupIdx][idx]->ApplyPolygon = val;
      }
      this->Modified();
    }
  }
}

void vtkClipPolygons::SetClipInsideOut(int idx, int val)
{
  if (this->IsActiveGroupValid())
  {
    if (idx >= 0 && idx < static_cast<int>(this->Polygons[this->ActiveGroupIdx].size()))
    {
      if (this->Polygons[this->ActiveGroupIdx][idx]->InsideOut != val)
      {
        this->Polygons[this->ActiveGroupIdx][idx]->InsideOut = val;
      }
      this->Modified();
    }
  }
}

void vtkClipPolygons::SetClipAsROI(int idx, int val)
{
  if (this->IsActiveGroupValid())
  {
    if (idx >= 0 && idx < static_cast<int>(this->Polygons[this->ActiveGroupIdx].size()))
    {
      if (this->Polygons[this->ActiveGroupIdx][idx]->AsROI != val)
      {
        this->Polygons[this->ActiveGroupIdx][idx]->AsROI = val;
        if (val)
        {
          this->Polygons[this->ActiveGroupIdx][idx]->InsideOut = 0;
        }
      }
      this->Modified();
    }
  }
}

void vtkClipPolygons::SetTransform(double elements[16])
{
  vtkTransform* tmpTransform = vtkTransform::New();
  tmpTransform->SetMatrix(elements);
  this->SetTransform(tmpTransform);
  tmpTransform->Delete();
}

// Overload standard modified time function. If Clip functions is modified,
// then this object is modified as well.
vtkMTimeType vtkClipPolygons::GetMTime()
{
  vtkMTimeType mTime = this->Superclass::GetMTime();
  vtkMTimeType time;

  for (std::map<int, std::vector<PolygonInfo*> >::iterator itMap = this->Polygons.begin();
       itMap != this->Polygons.end(); itMap++)
  {
    for (std::vector<PolygonInfo*>::iterator it = itMap->second.begin(); it != itMap->second.end();
         it++)
    {
      if ((*it)->Polygon)
      {
        time = (*it)->Polygon->GetMTime();
        mTime = (time > mTime ? time : mTime);
      }
    }
  }
  if (this->Locator != NULL)
  {
    time = this->Locator->GetMTime();
    mTime = (time > mTime ? time : mTime);
  }

  return mTime;
}

vtkPolyData* vtkClipPolygons::GetClippedOutput()
{
  return vtkPolyData::SafeDownCast(this->GetExecutive()->GetOutputData(1));
}

vtkAlgorithmOutput* vtkClipPolygons::GetClippedOutputPort()
{
  return this->GetOutputPort(1);
}

//
// Clip through data generating surface.
//
int vtkClipPolygons::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  if (this->IsProcessing)
  {
    vtkErrorMacro(<< "vtkClipPolygons::RequestData is called again while it is still processing");
    return 0;
  }

  // get the info objects
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  // get the input and output
  vtkPolyData* input = vtkPolyData::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkIdType cellId, i, updateTime;
  vtkPoints* cellPts;
  vtkDataArray* clipScalars;
  vtkFloatArray* cellScalars;
  vtkGenericCell* cell;
  vtkCellArray *newVerts, *newLines, *newPolys, *connList = NULL;
  vtkCellArray *clippedVerts = NULL, *clippedLines = NULL;
  vtkCellArray *clippedPolys = NULL, *clippedList = NULL;
  vtkPoints* newPoints;
  vtkIdList* cellIds;
  double s;
  vtkIdType estimatedSize, numCells = input->GetNumberOfCells();
  vtkIdType numPts = input->GetNumberOfPoints();
  vtkPoints* inPts = input->GetPoints();
  int numberOfPoints;
  vtkPointData *inPD = input->GetPointData(), *outPD = output->GetPointData();
  vtkCellData *inCD = input->GetCellData(), *outCD = output->GetCellData();
  vtkCellData* outClippedCD = NULL;

  vtkDebugMacro(<< "Clipping polygonal data");

  // Initialize self; create output objects
  //
  if (numPts < 1 || inPts == NULL)
  {
    vtkWarningMacro(<< "No data to clip");
    return 1;
  }

  this->IsProcessing = true;

  bool HasClipFunc = false;
  std::map<int, std::vector<PolygonInfo*> > ClipFuncs;
  //std::vector<PolygonInfo*> ClipROIs;
  //get all acitve polygons
  for (std::map<int, std::vector<PolygonInfo*> >::iterator itMap = this->Polygons.begin();
       itMap != this->Polygons.end(); itMap++)
  {
    for (std::vector<PolygonInfo*>::iterator it = itMap->second.begin(); it != itMap->second.end();
         it++)
    {
      if ((*it)->Polygon && (*it)->ApplyPolygon)
      {
        HasClipFunc = true;
        ClipFuncs[itMap->first].push_back(*it);
      }
      /*
      else
        {
        HasClipROI = true;
        ClipROIs.push_back(*it);
        }
*/
    }
  }

  if (!HasClipFunc)
  {
    output->ShallowCopy(input);
    this->IsProcessing = false;
    return 1;
  }
  this->UpdateProgress(0.0);

  vtkFloatArray* tmpScalars = vtkFloatArray::New();
  tmpScalars->SetNumberOfTuples(numPts);
  inPD = vtkPointData::New();
  inPD->ShallowCopy(input->GetPointData()); //copies original
                                            //  bool passAllClip;
                                            //  bool insideROI;
  bool shouldClip = false;
  double transformedPoint[3], *pointPtr;
  double point[3];
  for (i = 0; i < numPts; i++)
  {
    inPts->GetPoint(i, point);
    if (this->Transform)
    {
      this->Transform->TransformPoint(point, transformedPoint);
      pointPtr = transformedPoint;
    }
    else
    {
      pointPtr = point;
    }
    for (std::map<int, std::vector<PolygonInfo*> >::iterator itMap = this->Polygons.begin();
         itMap != this->Polygons.end(); itMap++)

      for (std::map<int, std::vector<PolygonInfo*> >::iterator itFuncs = ClipFuncs.begin();
           itFuncs != ClipFuncs.end(); itFuncs++)
      {
        shouldClip = true;
        for (std::vector<PolygonInfo*>::iterator itF = itFuncs->second.begin();
             itF != itFuncs->second.end(); itF++)
        {
          s = (*itF)->Polygon->FunctionValue(pointPtr);
          if ((!((*itF)->InsideOut) && s <= 0) || (((*itF)->InsideOut) && s > 0))
          {
            shouldClip = false;
            break;
          }
        }
        if (this->GroupInvert[itFuncs->first])
        {
          shouldClip = !shouldClip;
        }
        if (shouldClip)
        {
          break;
        }
      }
    s = shouldClip ? (this->Value - 1.0) : (this->Value + 1.0);
    tmpScalars->SetComponent(i, 0, s);
  }
  clipScalars = tmpScalars;

  // Create objects to hold output of clip operation
  //
  estimatedSize = numCells;
  estimatedSize = estimatedSize / 1024 * 1024; //multiple of 1024
  if (estimatedSize < 1024)
  {
    estimatedSize = 1024;
  }

  newPoints = vtkPoints::New();
  newPoints->Allocate(numPts, numPts / 2);
  newVerts = vtkCellArray::New();
  newVerts->Allocate(estimatedSize, estimatedSize / 2);
  newLines = vtkCellArray::New();
  newLines->Allocate(estimatedSize, estimatedSize / 2);
  newPolys = vtkCellArray::New();
  newPolys->Allocate(estimatedSize, estimatedSize / 2);

  // locator used to merge potentially duplicate points
  if (this->Locator == NULL)
  {
    this->CreateDefaultLocator();
  }
  this->Locator->InitPointInsertion(newPoints, input->GetBounds());

  if (!input->GetPointData()->GetScalars())
  {
    outPD->CopyScalarsOff();
  }
  else
  {
    outPD->CopyScalarsOn();
  }
  outPD->InterpolateAllocate(inPD, estimatedSize, estimatedSize / 2);
  outCD->CopyAllocate(inCD, estimatedSize, estimatedSize / 2);

  // If generating second output, setup clipped output
  if (this->GenerateClippedOutput)
  {
    this->GetClippedOutput()->Initialize();
    outClippedCD = this->GetClippedOutput()->GetCellData();
    outClippedCD->CopyAllocate(inCD, estimatedSize, estimatedSize / 2);
    clippedVerts = vtkCellArray::New();
    clippedVerts->Allocate(estimatedSize, estimatedSize / 2);
    clippedLines = vtkCellArray::New();
    clippedLines->Allocate(estimatedSize, estimatedSize / 2);
    clippedPolys = vtkCellArray::New();
    clippedPolys->Allocate(estimatedSize, estimatedSize / 2);
  }

  cellScalars = vtkFloatArray::New();
  cellScalars->Allocate(VTK_CELL_SIZE);

  // perform clipping on cells
  int abort = 0;
  updateTime = numCells / 20 + 1; // update roughly every 5%
  cell = vtkGenericCell::New();
  for (cellId = 0; cellId < numCells && !abort; cellId++)
  {
    input->GetCell(cellId, cell);
    cellPts = cell->GetPoints();
    cellIds = cell->GetPointIds();
    numberOfPoints = cellPts->GetNumberOfPoints();

    // evaluate implicit cutting function
    for (i = 0; i < numberOfPoints; i++)
    {
      s = clipScalars->GetComponent(cellIds->GetId(i), 0);
      cellScalars->InsertTuple(i, &s);
    }

    switch (cell->GetCellDimension())
    {
      case 0: //points are generated
        connList = newVerts;
        clippedList = clippedVerts;
        break;

      case 1: //lines are generated
        connList = newLines;
        clippedList = clippedLines;
        break;

      case 2: //triangles are generated
        connList = newPolys;
        clippedList = clippedPolys;
        break;

    } //switch

    cell->Clip(
      this->Value, cellScalars, this->Locator, connList, inPD, outPD, inCD, cellId, outCD, 0);

    if (this->GenerateClippedOutput)
    {
      cell->Clip(this->Value, cellScalars, this->Locator, clippedList, inPD, outPD, inCD, cellId,
        outClippedCD, 0);
    }

    // The UpdateProgress is somehow(?) causing the outPD's Arrays got cleared out!!
    if (!(cellId % updateTime))
    {
      this->UpdateProgress(static_cast<double>(cellId) / numCells);
      abort = this->GetAbortExecute();
    }
  } //for each cell
  cell->Delete();

  vtkDebugMacro(<< "Created: " << newPoints->GetNumberOfPoints() << " points, "
                << newVerts->GetNumberOfCells() << " verts, " << newLines->GetNumberOfCells()
                << " lines, " << newPolys->GetNumberOfCells() << " polys");

  if (this->GenerateClippedOutput)
  {
    vtkDebugMacro(<< "Created (clipped output): " << clippedVerts->GetNumberOfCells() << " verts, "
                  << clippedLines->GetNumberOfCells() << " lines, "
                  << clippedPolys->GetNumberOfCells() << " triangles");
  }

  // Update ourselves.  Because we don't know upfront how many verts, lines,
  // polys we've created, take care to reclaim memory.
  //

  if (this->Polygons.size())
  {
    clipScalars->Delete();
    inPD->Delete();
  }

  if (newVerts->GetNumberOfCells())
  {
    output->SetVerts(newVerts);
  }
  newVerts->Delete();

  if (newLines->GetNumberOfCells())
  {
    output->SetLines(newLines);
  }
  newLines->Delete();

  if (newPolys->GetNumberOfCells())
  {
    output->SetPolys(newPolys);
  }
  newPolys->Delete();

  if (this->GenerateClippedOutput)
  {
    this->GetClippedOutput()->SetPoints(newPoints);

    if (clippedVerts->GetNumberOfCells())
    {
      this->GetClippedOutput()->SetVerts(clippedVerts);
    }
    clippedVerts->Delete();

    if (clippedLines->GetNumberOfCells())
    {
      this->GetClippedOutput()->SetLines(clippedLines);
    }
    clippedLines->Delete();

    if (clippedPolys->GetNumberOfCells())
    {
      this->GetClippedOutput()->SetPolys(clippedPolys);
    }
    clippedPolys->Delete();

    this->GetClippedOutput()->GetPointData()->PassData(outPD);
    this->GetClippedOutput()->Squeeze();
  }

  output->SetPoints(newPoints);
  newPoints->Delete();
  cellScalars->Delete();

  this->Locator->Initialize(); //release any extra memory
  output->Squeeze();
  this->IsProcessing = false;
  this->UpdateProgress(1.0);

  return 1;
}

// Specify a spatial locator for merging points. By default,
// an instance of vtkMergePoints is used.
void vtkClipPolygons::SetLocator(vtkIncrementalPointLocator* locator)
{
  if (this->Locator == locator)
  {
    return;
  }

  if (this->Locator)
  {
    this->Locator->UnRegister(this);
    this->Locator = NULL;
  }

  if (locator)
  {
    locator->Register(this);
  }

  this->Locator = locator;
  this->Modified();
}

void vtkClipPolygons::CreateDefaultLocator()
{
  if (this->Locator == NULL)
  {
    this->Locator = vtkMergePoints::New();
  }
}

int vtkClipPolygons::GetNumberOfActivePolygons()
{
  int ret = 0;
  for (std::map<int, std::vector<PolygonInfo*> >::iterator itMap = this->Polygons.begin();
       itMap != this->Polygons.end(); itMap++)
  {
    for (std::vector<PolygonInfo*>::iterator it = itMap->second.begin(); it != itMap->second.end();
         it++)
    {
      if ((*it)->Polygon && (*it)->ApplyPolygon)
      {
        ret++;
      }
    }
  }
  return ret;
}

void vtkClipPolygons::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  /*
  if ( this->ClipFunction )
    {
    os << indent << "Clip Function: " << this->ClipFunction << "\n";
    }
  else
    {
    os << indent << "Clip Function: (none)\n";
    }
  os << indent << "InsideOut: " << (this->InsideOut ? "On\n" : "Off\n");
  os << indent << "Value: " << this->Value << "\n";
*/
  if (this->Locator)
  {
    os << indent << "Locator: " << this->Locator << "\n";
  }
  else
  {
    os << indent << "Locator: (none)\n";
  }

  os << indent << "Generate Clipped Output: " << (this->GenerateClippedOutput ? "On\n" : "Off\n");
}
