//=========================================================================
//  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 "vtkCMBArcProvider.h"

#include "vtkCMBArc.h"
#include "vtkCMBArcEndNode.h"
#include "vtkCMBArcManager.h"

#include "vtkCellArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkUnsignedIntArray.h"

vtkStandardNewMacro(vtkCMBArcProvider);

vtkCMBArcProvider::vtkCMBArcProvider()
{
  this->ArcId = -1;
  this->Arc = NULL;
  this->ArcManager = vtkCMBArcManager::GetInstance();

  this->SetNumberOfInputPorts(0);
  this->SetNumberOfOutputPorts(1);
}

vtkCMBArcProvider::~vtkCMBArcProvider()
{
  this->ArcId = -1;
  this->StartPointId = -1;
  this->EndPointId = -1;
  this->Arc = NULL;
  this->ArcManager = NULL;
}

void vtkCMBArcProvider::SetArcId(vtkIdType arcId)
{
  if (this->ArcId != arcId)
  {
    //we need to keep a reference to the arc so that
    //we can properly report the correct MTime if any of the end nodes change
    this->ArcId = arcId;
    this->StartPointId = -1;
    this->EndPointId = -1;
    this->Arc = this->ArcManager->GetArc(this->ArcId);
    this->Modified();
  }
}

void vtkCMBArcProvider::SetStartPointId(vtkIdType ptId)
{
  if (this->StartPointId != ptId)
  {
    this->StartPointId = ptId;
    this->Modified();
  }
}

void vtkCMBArcProvider::SetEndPointId(vtkIdType ptId)
{
  if (this->EndPointId != ptId)
  {
    this->EndPointId = ptId;
    this->Modified();
  }
}

vtkMTimeType vtkCMBArcProvider::GetMTime()
{
  vtkMTimeType mTime = this->Superclass::GetMTime();
  if (this->Arc)
  {
    vtkMTimeType time = this->Arc->GetMTime();
    mTime = (time > mTime ? time : mTime);
  }
  return mTime;
}

vtkPolyData* vtkCMBArcProvider::CreatePolyDataRepresentation()
{
  vtkPolyData* representation = vtkPolyData::New();
  int numEndNodes = this->Arc->GetNumberOfEndNodes();
  if (numEndNodes == 0)
  {
    //we have no end nodes so we are empty
    return representation;
  }

  //see if both end nodes are the same
  bool closedArc = this->Arc->IsClosedArc();

  //construct a single polyline from the data
  vtkIdType pointSize = this->Arc->GetNumberOfArcPoints();

  vtkIdType cellSize = pointSize;
  cellSize += static_cast<int>(closedArc);

  vtkPoints* points = vtkPoints::New();

  vtkCellArray* vertCell = vtkCellArray::New();

  vtkIdTypeArray* cellArray = vtkIdTypeArray::New();
  vtkCellArray* cells = vtkCellArray::New();

  vtkSmartPointer<vtkUnsignedIntArray> ids = vtkSmartPointer<vtkUnsignedIntArray>::New();
  ids->SetNumberOfComponents(1);
  ids->SetName("PointIDs");

  points->SetNumberOfPoints(pointSize);
  ids->SetNumberOfTuples(pointSize);
  cellArray->SetNumberOfValues(cellSize + 1); //make room for the cell size value
  vertCell->InsertNextCell(numEndNodes);

  //setup the first end node as the first point, and the first point id in the cell array
  vtkIdType index = 0;
  cellArray->SetValue(index, cellSize); //setup the size of the cell
  points->SetPoint(index, this->Arc->GetEndNode(0)->GetPosition());
  ids->SetTuple1(index, this->Arc->GetEndNode(0)->GetPointId());
  vertCell->InsertCellPoint(index);
  cellArray->SetValue(index + 1, index);
  ++index;

  //add all the points in the internal point list
  this->Arc->InitTraversal();
  //TODO Save id to output polydata
  vtkCMBArc::Point point;
  while (this->Arc->GetNextPoint(point))
  {
    points->SetPoint(index, point[0], point[1], point[2]);
    ids->SetTuple1(index, point.GetId());
    cellArray->SetValue(index + 1, index);
    ++index;
  }

  //finish off with the last end node
  if (closedArc)
  {
    cellArray->SetValue(index + 1, 0);
  }
  else if (numEndNodes == 2)
  {
    points->SetPoint(index, this->Arc->GetEndNode(1)->GetPosition());
    ids->SetTuple1(index, this->Arc->GetEndNode(1)->GetPointId());
    vertCell->InsertCellPoint(index);
    cellArray->SetValue(index + 1, index);
  }

  cells->SetCells(1, cellArray);
  cellArray->Delete();

  representation->SetPoints(points);
  points->FastDelete();

  representation->GetPointData()->SetScalars(ids);

  representation->SetVerts(vertCell);
  vertCell->FastDelete();

  representation->SetLines(cells);
  cells->FastDelete();

  representation->Squeeze();
  return representation;
}

vtkPolyData* vtkCMBArcProvider::CreateSubArcPolyDataRepresentation()
{
  vtkIdType numPoints = (this->EndPointId >= this->StartPointId)
    ? (this->EndPointId - this->StartPointId + 1)
    : (this->StartPointId - this->EndPointId + 1);

  vtkPolyData* representation = vtkPolyData::New();
  int numEndNodes = this->Arc->GetNumberOfEndNodes();
  if (numEndNodes == 0 || numPoints <= 1)
  {
    // we have no end nodes, or requested points are not enough for a sub-arc,
    // so we are empty
    return representation;
  }

  // construct a single polyline from the data,
  // and two verts for the two end points of the sub-arc

  //see if both end nodes are the same
  bool closedArc = this->Arc->IsClosedArc();
  bool forwardDir = this->EndPointId > this->StartPointId;
  bool invertClosedArc = false;
  // If the arc is closed loop, and the end-point is the end node, we will select
  // the sub-arc (startIndex, ..., n-1, n, 0), NOT (startIdex, startIndex-1,..., 1, 0).
  // this way all points of the loop arc can be selectable.
  if (closedArc && this->EndPointId < this->StartPointId)
  {
    forwardDir = true;
    invertClosedArc = true;
    numPoints = this->Arc->GetNumberOfArcPoints() - numPoints + 2;
  }

  // Since we are dealing only with sub-arcs here (previously should have
  // eliminated the full arc case), we need to figure out whether one of
  // the end nodes of the arc is picked, if yes, we have to adjust the numPoints
  // and startPointId to traverse the internal points of the arc.
  vtkIdType pointSize = numPoints;

  vtkPoints* points = vtkPoints::New();
  vtkCellArray* vertCell = vtkCellArray::New();
  vtkIdTypeArray* cellArray = vtkIdTypeArray::New();
  vtkCellArray* cells = vtkCellArray::New();
  vtkSmartPointer<vtkUnsignedIntArray> ids = vtkSmartPointer<vtkUnsignedIntArray>::New();
  ids->SetNumberOfComponents(1);
  ids->SetName("PointIDs");

  //the sub-arc can only be an open line
  vtkIdType cellSize = pointSize;
  points->SetNumberOfPoints(pointSize);
  ids->SetNumberOfTuples(pointSize);

  cellArray->SetNumberOfValues(cellSize + 1);
  cellArray->SetValue(0, cellSize); //setup the size of the cell

  //setup the first end node as the first point, and the first point id in the cell array
  vtkIdType index = 0;
  int startIndex = this->StartPointId;
  // if start point is an end node
  if (this->StartPointId == 0 ||
    (this->StartPointId == this->Arc->GetNumberOfArcPoints() - 1 && !closedArc))
  {
    int nodeIndex = this->StartPointId == 0 ? 0 : 1;
    points->SetPoint(index, this->Arc->GetEndNode(nodeIndex)->GetPosition());
    ids->SetTuple1(index, this->Arc->GetEndNode(nodeIndex)->GetPointId());
    vertCell->InsertNextCell(1, &index);
    cellArray->SetValue(index + 1, index);
    // then set the start point for arc traversal
    startIndex = this->StartPointId == 0 ? 0 : this->Arc->GetNumberOfInternalPoints() - 1;
    --numPoints;
    ++index;
  }
  else
  {
    --startIndex; // for arc internal points
  }
  // if end point is an end node, or requesting invertRange on a closed arc,
  // requested points from internal arc points will be 1 less.
  bool endPtisEndNode = (this->EndPointId == 0 ||
    (this->EndPointId == this->Arc->GetNumberOfArcPoints() - 1 && !closedArc));
  if (endPtisEndNode)
  {
    --numPoints;
  }
  else if (invertClosedArc)
  {
    numPoints = this->Arc->GetNumberOfInternalPoints() - this->StartPointId + 1;
  }

  //add all the points in the internal point list
  this->Arc->InitTraversal(startIndex, numPoints, forwardDir);
  // if this is the very first point, make it a vertex
  vtkCMBArc::Point point;
  if (index == 0)
  {
    if (this->Arc->GetNextPoint(point))
    {
      points->SetPoint(index, point[0], point[1], point[2]);
      ids->SetTuple1(index, point.GetId());
      vertCell->InsertNextCell(1, &index);
      cellArray->SetValue(index + 1, index);
      ++index;
    }
  }

  // This is just traversing the internal points of the arc
  while (this->Arc->GetNextPoint(point))
  {
    points->SetPoint(index, point[0], point[1], point[2]);
    ids->SetTuple1(index, point.GetId());
    cellArray->SetValue(index + 1, index);
    ++index;
  }
  // if end point is an end node
  if (endPtisEndNode)
  {
    int nodeIndex = this->EndPointId == 0 ? 0 : 1;
    points->SetPoint(pointSize - 1, this->Arc->GetEndNode(nodeIndex)->GetPosition());
    ids->SetTuple1(pointSize - 1, this->Arc->GetEndNode(nodeIndex)->GetPointId());
    cellArray->SetValue(pointSize, pointSize - 1);
  }
  // if we are doing an invert on a closed arc, and the end point is not an end node
  // which means we reached the end node in the middle of the traversal, we need to get that
  // node, then continue.
  else if (invertClosedArc && index < pointSize)
  {
    points->SetPoint(index, this->Arc->GetEndNode(0)->GetPosition());
    ids->SetTuple1(index, this->Arc->GetEndNode(0)->GetPointId());
    cellArray->SetValue(index + 1, index);
    index++;
    //add all the points in the internal point list
    this->Arc->InitTraversal(0, pointSize - index, forwardDir);
    // traversing from start
    while (this->Arc->GetNextPoint(point))
    {
      points->SetPoint(index, point[0], point[1], point[2]);
      ids->SetTuple1(index, point.GetId());
      cellArray->SetValue(index + 1, index);
      ++index;
    }
  }
  // make the last point a vertex.
  vtkIdType vtxId = pointSize - 1;
  vertCell->InsertNextCell(1, &vtxId);

  cells->SetCells(1, cellArray);
  cellArray->Delete();

  representation->SetPoints(points);
  points->FastDelete();

  representation->GetPointData()->SetScalars(ids);

  representation->SetVerts(vertCell);
  vertCell->FastDelete();

  representation->SetLines(cells);
  cells->FastDelete();

  representation->Squeeze();
  return representation;
}

int vtkCMBArcProvider::RequestData(
  vtkInformation*, vtkInformationVector**, vtkInformationVector* outputVector)
{

  if (!this->Arc)
  {
    vtkErrorMacro("Unable to create an arc representation as given invalid arc id");
    return 1;
  }

  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  //see if both end nodes are the same
  vtkPolyData* arcRep;
  // by default we always use the whole arc, unless the sub arc is specified
  // if whole arc is requested, use original method
  // otherwise, create polydata for sub-arc.
  if (vtkCMBArc::IsWholeArcRange(this->StartPointId, this->EndPointId,
        this->Arc->GetNumberOfArcPoints(), this->Arc->IsClosedArc()))
  {
    arcRep = this->CreatePolyDataRepresentation();
  }
  else
  {
    arcRep = this->CreateSubArcPolyDataRepresentation();
  }
  output->Initialize();
  output->ShallowCopy(arcRep);
  arcRep->FastDelete();

  return 1;
}

void vtkCMBArcProvider::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "ArcId: " << this->ArcId << endl;
  os << indent << "StartPointId: " << this->StartPointId << endl;
  os << indent << "EndPointId: " << this->EndPointId << endl;
}
