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

  Program:   Visualization Toolkit
  Module:    vtkSelectPolyData.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.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 "vtkSelectPolyData.h"

#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkCharArray.h"
#include "vtkExecutive.h"
#include "vtkFloatArray.h"
#include "vtkGarbageCollector.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkLine.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkPolygon.h"
#include "vtkTriangleFilter.h"
#include "vtkTriangleStrip.h"

vtkStandardNewMacro(vtkSelectPolyData);

vtkCxxSetObjectMacro(vtkSelectPolyData, Loop, vtkPoints);

// Description:
// Instantiate object with InsideOut turned off.
vtkSelectPolyData::vtkSelectPolyData()
{
  this->GenerateSelectionScalars = 0;
  this->InsideOut = 0;
  this->Loop = nullptr;
  this->SelectionMode = VTK_INSIDE_SMALLEST_REGION;
  this->ClosestPoint[0] = this->ClosestPoint[1] = this->ClosestPoint[2] = 0.0;
  this->GenerateUnselectedOutput = 0;

  this->SetNumberOfOutputPorts(3);

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

  vtkPolyData* output3 = vtkPolyData::New();
  this->GetExecutive()->SetOutputData(2, output3);
  output3->Delete();
}

//------------------------------------------------------------------------------
vtkSelectPolyData::~vtkSelectPolyData()
{
  if (this->Loop)
  {
    this->Loop->Delete();
  }
}

//------------------------------------------------------------------------------
vtkPolyData* vtkSelectPolyData::GetUnselectedOutput()
{
  if (this->GetNumberOfOutputPorts() < 2)
  {
    return nullptr;
  }

  return vtkPolyData::SafeDownCast(this->GetExecutive()->GetOutputData(1));
}

//------------------------------------------------------------------------------
vtkPolyData* vtkSelectPolyData::GetSelectionEdges()
{
  if (this->GetNumberOfOutputPorts() < 3)
  {
    return nullptr;
  }

  return vtkPolyData::SafeDownCast(this->GetExecutive()->GetOutputData(2));
}

//------------------------------------------------------------------------------
int vtkSelectPolyData::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  // 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 numPts, numLoopPts;
  vtkPolyData* triMesh;
  vtkPointData *inPD, *outPD = output->GetPointData();
  vtkCellData *inCD, *outCD = output->GetCellData();
  vtkIdType closest = 0, numPolys, i, j;
  int k;
  vtkIdList *loopIds, *edgeIds, *neighbors;
  double x[3], xLoop[3], closestDist2, dist2, t;
  double x0[3], x1[3], vec[3], dir[3], neiX[3];
  vtkCellArray* inPolys;
  vtkPoints* inPts;
  int numNei;
  vtkIdType id, currentId = 0, nextId, pt1, pt2, numCells, neiId;
  vtkIdType* cells;
  const vtkIdType* pts;
  vtkIdType npts;
  vtkIdType numMeshLoopPts;
  vtkIdType prevId;
  vtkIdType ncells;
  int mark, s1, s2, val;

  // Initialize and check data
  vtkDebugMacro(<< "Selecting data...");

  this->GetUnselectedOutput()->Initialize();
  this->GetSelectionEdges()->Initialize();

  if ((numPts = input->GetNumberOfPoints()) < 1)
  {
    vtkErrorMacro("Input contains no points");
    return 1;
  }

  if (this->Loop == nullptr || (numLoopPts = this->Loop->GetNumberOfPoints()) < 3)
  {
    vtkErrorMacro("Please define a loop with at least three points");
    return 1;
  }

  // Okay, now we build unstructured representation. Make sure we're
  // working with triangles.
  vtkTriangleFilter* tf = vtkTriangleFilter::New();
  tf->SetInputData(input);
  tf->PassLinesOff();
  tf->PassVertsOff();
  tf->Update();
  triMesh = tf->GetOutput();
  triMesh->Register(this);
  tf->Delete();
  inPD = triMesh->GetPointData();
  inCD = triMesh->GetCellData();

  numPts = triMesh->GetNumberOfPoints();
  inPts = triMesh->GetPoints();
  inPolys = triMesh->GetPolys();
  numPolys = inPolys->GetNumberOfCells();
  if (numPolys < 1)
  {
    vtkErrorMacro("This filter operates on surface primitives");
    tf->Delete();
    triMesh->UnRegister(this);
    return 1;
  }

  this->Mesh = vtkPolyData::New();
  this->Mesh->SetPoints(inPts); // inPts
  this->Mesh->SetPolys(inPolys);
  this->Mesh->BuildLinks(); // to do neighborhood searching
  numCells = this->Mesh->GetNumberOfCells();

  // First thing to do is find the closest mesh points to the loop
  // points. This creates a list of point ids.
  loopIds = vtkIdList::New();
  loopIds->SetNumberOfIds(numLoopPts);

  for (i = 0; i < numLoopPts; i++)
  {
    this->Loop->GetPoint(i, xLoop);
    closest = -1;
    closestDist2 = VTK_DOUBLE_MAX;

    for (j = 0; j < numPts; j++)
    {
      inPts->GetPoint(j, x);

      dist2 = vtkMath::Distance2BetweenPoints(x, xLoop);
      if (dist2 < closestDist2)
      {
        closest = j;
        closestDist2 = dist2;
      }
    } // for all input points

    loopIds->SetId(i, closest);
  } // for all loop points

  // Now that we've got point ids, we build the loop. Start with the
  // first two points in the loop (which define a line), and find the
  // mesh edge that is directed along the line, and whose
  // end point is closest to the line. Continue until loop closes in on
  // itself.
  edgeIds = vtkIdList::New();
  edgeIds->Allocate(numLoopPts * 10, 1000);
  neighbors = vtkIdList::New();
  neighbors->Allocate(10000);
  edgeIds->InsertNextId(loopIds->GetId(0));

  for (i = 0; i < numLoopPts; i++)
  {
    currentId = loopIds->GetId(i);
    nextId = loopIds->GetId((i + 1) % numLoopPts);
    prevId = (-1);
    inPts->GetPoint(currentId, x);
    inPts->GetPoint(currentId, x0);
    inPts->GetPoint(nextId, x1);
    for (j = 0; j < 3; j++)
    {
      vec[j] = x1[j] - x0[j];
    }

    // track edge
    for (id = currentId; id != nextId;)
    {
      this->GetPointNeighbors(id, neighbors); // points connected by edge
      numNei = neighbors->GetNumberOfIds();
      closest = -1;
      closestDist2 = VTK_DOUBLE_MAX;
      for (j = 0; j < numNei; j++)
      {
        neiId = neighbors->GetId(j);
        if (neiId == nextId)
        {
          closest = neiId;
          break;
        }
        else
        {
          inPts->GetPoint(neiId, neiX);
          for (k = 0; k < 3; k++)
          {
            dir[k] = neiX[k] - x[k];
          }
          if (neiId != prevId && vtkMath::Dot(dir, vec) > 0.0) // candidate
          {
            dist2 = vtkLine::DistanceToLine(neiX, x0, x1);
            if (dist2 < closestDist2)
            {
              closest = neiId;
              closestDist2 = dist2;
            }
          } // in direction of line
        }
      } // for all neighbors

      if (closest < 0)
      {
        vtkErrorMacro(<< "Can't follow edge");
        triMesh->UnRegister(this);
        this->Mesh->Delete();
        neighbors->Delete();
        edgeIds->Delete();
        loopIds->Delete();
        return 1;
      }
      else
      {
        edgeIds->InsertNextId(closest);
        prevId = id;
        id = closest;
        inPts->GetPoint(id, x);
      }
    } // for tracking edge
  }   // for all edges of loop

  // mainly for debugging
  numMeshLoopPts = edgeIds->GetNumberOfIds();
  vtkCellArray* selectionEdges = vtkCellArray::New();
  selectionEdges->AllocateEstimate(1, numMeshLoopPts);
  selectionEdges->InsertNextCell(numMeshLoopPts);
  for (i = 0; i < numMeshLoopPts; i++)
  {
    selectionEdges->InsertCellPoint(edgeIds->GetId(i));
  }
  this->GetSelectionEdges()->SetPoints(inPts);
  this->GetSelectionEdges()->SetLines(selectionEdges);
  selectionEdges->Delete();

  // Phew...we've defined loop, now want to do a fill so we can extract the
  // inside from the outside. Depending on GenerateSelectionScalars flag,
  // we either set the distance of the points to the selection loop as
  // zero (GenerateSelectionScalarsOff) or we evaluate the distance of these
  // points to the lines. (Later we'll use a connected traversal to compute
  // the distances to the remaining points.)

  // Next, prepare to mark off inside/outside and on boundary of loop.
  // Mark the boundary of the loop using point marks. Also initialize
  // the advancing front (used to mark traversal/compute scalars).
  // Prepare to compute the advancing front
  vtkIntArray* cellMarks = vtkIntArray::New();
  cellMarks->SetNumberOfValues(numCells);
  for (i = 0; i < numCells; i++) // mark unvisited
  {
    cellMarks->SetValue(i, VTK_INT_MAX);
  }
  vtkIntArray* pointMarks = vtkIntArray::New();
  pointMarks->SetNumberOfValues(numPts);
  for (i = 0; i < numPts; i++) // mark unvisited
  {
    pointMarks->SetValue(i, VTK_INT_MAX);
  }

  vtkIdList *currentFront = vtkIdList::New(), *tmpFront;
  vtkIdList* nextFront = vtkIdList::New();
  for (i = 0; i < numMeshLoopPts; i++)
  {
    id = edgeIds->GetId(i);
    pointMarks->SetValue(id, 0); // marks the start of the front
    currentFront->InsertNextId(id);
  }

  // Traverse the front as long as we can. We're basically computing a
  // topological distance.
  int maxFront = 0;
  vtkIdType maxFrontCell = (-1);
  int currentFrontNumber = 1, numPtsInFront;
  while ((numPtsInFront = currentFront->GetNumberOfIds()))
  {
    for (i = 0; i < numPtsInFront; i++)
    {
      id = currentFront->GetId(i);
      this->Mesh->GetPointCells(id, ncells, cells);
      for (j = 0; j < ncells; j++)
      {
        id = cells[j];
        if (cellMarks->GetValue(id) == VTK_INT_MAX)
        {
          if (currentFrontNumber > maxFront)
          {
            maxFrontCell = id;
          }
          cellMarks->SetValue(id, currentFrontNumber);
          this->Mesh->GetCellPoints(id, npts, pts);
          for (k = 0; k < npts; k++)
          {
            if (pointMarks->GetValue(pts[k]) == VTK_INT_MAX)
            {
              pointMarks->SetValue(pts[k], 1);
              nextFront->InsertNextId(pts[k]);
            }
          }
        }
      } // for cells surrounding point
    }   // all points in front

    currentFrontNumber++;
    tmpFront = currentFront;
    currentFront = nextFront;
    nextFront = tmpFront;
    nextFront->Reset();
  } // while still advancing

  // Okay, now one of the regions is filled with negative values. This fill
  // operation assumes that everything is connected.
  if (this->SelectionMode == VTK_INSIDE_CLOSEST_POINT_REGION)
  { // find closest point and use as a seed
    for (closestDist2 = VTK_DOUBLE_MAX, j = 0; j < numPts; j++)
    {
      inPts->GetPoint(j, x);

      dist2 = vtkMath::Distance2BetweenPoints(x, this->ClosestPoint);
      // get closest point not on the boundary
      if (dist2 < closestDist2 && pointMarks->GetValue(j) != 0)
      {
        closest = j;
        closestDist2 = dist2;
      }
    } // for all input points
    this->Mesh->GetPointCells(closest, ncells, cells);
  }

  // We do the fill as a moving front. This is an alternative to recursion. The
  // fill negates one region of the mesh on one side of the loop.
  currentFront->Reset();
  nextFront->Reset();
  currentFront->InsertNextId(maxFrontCell);
  vtkIdType numCellsInFront;

  cellMarks->SetValue(maxFrontCell, -1);

  while ((numCellsInFront = currentFront->GetNumberOfIds()) > 0)
  {
    for (i = 0; i < numCellsInFront; i++)
    {
      id = currentFront->GetId(i);

      this->Mesh->GetCellPoints(id, npts, pts);
      for (j = 0; j < 3; j++)
      {
        pt1 = pts[j];
        pt2 = pts[(j + 1) % 3];
        s1 = pointMarks->GetValue(pt1);
        s2 = pointMarks->GetValue(pt2);

        if (s1 != 0)
        {
          pointMarks->SetValue(pt1, -1);
        }

        if (!(s1 == 0 && s2 == 0))
        {
          this->Mesh->GetCellEdgeNeighbors(id, pt1, pt2, neighbors);
          numNei = neighbors->GetNumberOfIds();
          for (k = 0; k < numNei; k++)
          {
            neiId = neighbors->GetId(k);
            val = cellMarks->GetValue(neiId);
            if (val != -1) //-1 is what we're filling with
            {
              cellMarks->SetValue(neiId, -1);
              nextFront->InsertNextId(neiId);
            }
          }
        } // if can cross boundary
      }   // for all edges of cell
    }     // all cells in front

    tmpFront = currentFront;
    currentFront = nextFront;
    nextFront = tmpFront;
    nextFront->Reset();
  } // while still advancing

  // Now may have to invert fill value depending on what we want to extract
  if (this->SelectionMode == VTK_INSIDE_SMALLEST_REGION)
  {
    for (i = 0; i < numCells; i++)
    {
      mark = cellMarks->GetValue(i);
      cellMarks->SetValue(i, -mark);
    }
    for (i = 0; i < numPts; i++)
    {
      mark = pointMarks->GetValue(i);
      pointMarks->SetValue(i, -mark);
    }
  }

  // If generating selection scalars, we now have to modify the scalars to
  // approximate a distance function. Otherwise, we can create the output.
  if (!this->GenerateSelectionScalars)
  { // spit out all the negative cells
    vtkCellArray* newPolys = vtkCellArray::New();
    newPolys->AllocateEstimate(numCells / 2, numCells / 2);
    for (i = 0; i < numCells; i++)
    {
      if ((cellMarks->GetValue(i) < 0) || (cellMarks->GetValue(i) > 0 && this->InsideOut))
      {
        this->Mesh->GetCellPoints(i, npts, pts);
        newPolys->InsertNextCell(npts, pts);
      }
    }
    output->SetPoints(inPts);
    output->SetPolys(newPolys);
    outPD->PassData(inPD);
    newPolys->Delete();

    if (this->GenerateUnselectedOutput)
    {
      vtkCellArray* unPolys = vtkCellArray::New();
      unPolys->AllocateEstimate(numCells / 2, numCells / 2);
      for (i = 0; i < numCells; i++)
      {
        if ((cellMarks->GetValue(i) >= 0) || this->InsideOut)
        {
          this->Mesh->GetCellPoints(i, npts, pts);
          unPolys->InsertNextCell(npts, pts);
        }
      }
      this->GetUnselectedOutput()->SetPoints(inPts);
      this->GetUnselectedOutput()->SetPolys(unPolys);
      this->GetUnselectedOutput()->GetPointData()->PassData(inPD);
      unPolys->Delete();
    }
  }
  else // modify scalars to generate selection scalars
  {
    vtkFloatArray* selectionScalars = vtkFloatArray::New();
    selectionScalars->SetNumberOfTuples(numPts);

    // compute distance to lines. Really this should be computed based on
    // the connected fill distance.
    for (j = 0; j < numPts; j++) // compute minimum distance to loop
    {
      if (pointMarks->GetValue(j) != 0)
      {
        inPts->GetPoint(j, x);
        for (closestDist2 = VTK_DOUBLE_MAX, i = 0; i < numLoopPts; i++)
        {
          this->Loop->GetPoint(i, x0);
          this->Loop->GetPoint((i + 1) % numLoopPts, x1);
          dist2 = vtkLine::DistanceToLine(x, x0, x1, t, xLoop);
          if (dist2 < closestDist2)
          {
            closestDist2 = dist2;
          }
        } // for all loop edges
        closestDist2 = sqrt((double)closestDist2);
        selectionScalars->SetComponent(j, 0, closestDist2 * pointMarks->GetValue(j));
      }
    }

    // Now, determine the sign of those points "on the boundary" to give a
    // better approximation to the scalar field.
    for (j = 0; j < numMeshLoopPts; j++)
    {
      id = edgeIds->GetId(j);
      inPts->GetPoint(id, x);
      for (closestDist2 = VTK_DOUBLE_MAX, i = 0; i < numLoopPts; i++)
      {
        this->Loop->GetPoint(i, x0);
        this->Loop->GetPoint((i + 1) % numLoopPts, x1);
        dist2 = vtkLine::DistanceToLine(x, x0, x1, t, xLoop);
        if (dist2 < closestDist2)
        {
          closestDist2 = dist2;
          neiX[0] = xLoop[0];
          neiX[1] = xLoop[1];
          neiX[2] = xLoop[2];
        }
      } // for all loop edges
      closestDist2 = sqrt((double)closestDist2);

      // find neighbor not on boundary and compare negative/positive values
      // to see what makes the most sense
      this->GetPointNeighbors(id, neighbors);
      numNei = neighbors->GetNumberOfIds();
      for (dist2 = 0.0, i = 0; i < numNei; i++)
      {
        neiId = neighbors->GetId(i);
        if (pointMarks->GetValue(neiId) != 0) // find the furthest away
        {
          if (fabs(selectionScalars->GetComponent(neiId, 0)) > dist2)
          {
            currentId = neiId;
            dist2 = fabs(selectionScalars->GetComponent(neiId, 0));
          }
        }
      }

      inPts->GetPoint(currentId, x0);
      if (vtkMath::Distance2BetweenPoints(x0, x) < vtkMath::Distance2BetweenPoints(x0, neiX))
      {
        closestDist2 = closestDist2 * pointMarks->GetValue(currentId);
      }
      else
      {
        closestDist2 = -closestDist2 * pointMarks->GetValue(currentId);
      }

      selectionScalars->SetComponent(id, 0, closestDist2);
    } // for all boundary points

    output->CopyStructure(this->Mesh); // pass geometry/topology unchanged
    int idx = outPD->AddArray(selectionScalars);
    outPD->SetActiveAttribute(idx, vtkDataSetAttributes::SCALARS);
    outPD->CopyScalarsOff();
    outPD->PassData(inPD);
    outCD->PassData(inCD);
    selectionScalars->Delete();
  }

  // Clean up and update output
  triMesh->UnRegister(this);
  this->Mesh->Delete();
  neighbors->Delete();
  edgeIds->Delete();
  loopIds->Delete();
  cellMarks->Delete();
  pointMarks->Delete();
  currentFront->Delete();
  nextFront->Delete();

  return 1;
}

//------------------------------------------------------------------------------
void vtkSelectPolyData::GetPointNeighbors(vtkIdType ptId, vtkIdList* nei)
{
  vtkIdType ncells;
  int i, j;
  vtkIdType* cells;
  const vtkIdType* pts;
  vtkIdType npts;

  nei->Reset();
  this->Mesh->GetPointCells(ptId, ncells, cells);
  for (i = 0; i < ncells; i++)
  {
    this->Mesh->GetCellPoints(cells[i], npts, pts);
    for (j = 0; j < 3; j++)
    {
      if (pts[j] != ptId)
      {
        nei->InsertUniqueId(pts[j]);
      }
    }
  }
}

//------------------------------------------------------------------------------
vtkMTimeType vtkSelectPolyData::GetMTime()
{
  vtkMTimeType mTime = this->Superclass::GetMTime();
  vtkMTimeType time;

  if (this->Loop != nullptr)
  {
    time = this->Loop->GetMTime();
    mTime = (time > mTime ? time : mTime);
  }

  return mTime;
}

//------------------------------------------------------------------------------
void vtkSelectPolyData::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent
     << "Generate Unselected Output: " << (this->GenerateUnselectedOutput ? "On\n" : "Off\n");

  os << indent << "Inside Mode: ";
  os << this->GetSelectionModeAsString() << "\n";

  os << indent << "Closest Point: (" << this->ClosestPoint[0] << ", " << this->ClosestPoint[1]
     << ", " << this->ClosestPoint[2] << ")\n";

  os << indent
     << "Generate Selection Scalars: " << (this->GenerateSelectionScalars ? "On\n" : "Off\n");

  os << indent << "Inside Out: " << (this->InsideOut ? "On\n" : "Off\n");

  if (this->Loop)
  {
    os << indent << "Loop of " << this->Loop->GetNumberOfPoints() << "points defined\n";
  }
  else
  {
    os << indent << "Loop not defined\n";
  }
}
