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

  Program:   Visualization Toolkit
  Module:    vtkCMBArcManager.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 "vtkCMBArcManager.h"
#include "vtkCMBArc.h"
#include "vtkCMBArcEndNode.h"

#include "vtkObjectFactory.h"
#include "vtkDebugLeaks.h"
#include "vtkDebugLeaksManager.h"
#include "vtkIncrementalOctreePointLocator.h"
#include "vtkPolyData.h"
#include "vtkPoints.h"
#include "vtkNew.h"

#include <iterator>
#include <algorithm>

//----------------------------------------------------------------------------

vtkCMBArcManager* vtkCMBArcManager::Instance = 0;
vtkCMBArcManagerCleanup vtkCMBArcManager::Cleanup;

//-----------------------------------------------------------------------------
vtkCMBArcManagerCleanup::vtkCMBArcManagerCleanup()
{
}
//-----------------------------------------------------------------------------
vtkCMBArcManagerCleanup::~vtkCMBArcManagerCleanup()
{
  vtkCMBArcManager::SetInstance(NULL);
}

//-----------------------------------------------------------------------------
vtkCMBArcManager::vtkCMBArcManager( std::set<vtkCMBArc*> subset)
  :
  PointLocator(NULL),
  LocatorBounds(),
  Arcs(vtkCMBArcMap()),
  UndoArcs(vtkCMBArcSet()),
  PointIdsToEndNodes(vtkPointIdsToEndNodeMap()),
  EndNodesToArcs(vtkCmbEndNodesToArcMap()),
  UseSnapping(false),
  SnapRadius(0),
  SnapRadiusSquared(0),
  LocatorNeedsRebuilding(true)
{
  this->BuildLocator(); //need a locator before we add any end nodes or we crash

  std::set<vtkCMBArc*>::iterator it;
  vtkCMBArc *arc;
  for (it=subset.begin(); it != subset.end(); ++it)
    {
    //add each arc to this subset manager so we have info
    arc = *it;
    this->RegisterArc(arc);
    this->AddEndNode(arc->GetEndNode(0),arc);
    this->AddEndNode(arc->GetEndNode(1),arc);
    }
  this->BuildLocator();
}

//-----------------------------------------------------------------------------
vtkCMBArcManager::vtkCMBArcManager()
  :
  PointLocator(NULL),
  LocatorBounds(),
  Arcs(vtkCMBArcMap()),
  UndoArcs(vtkCMBArcSet()),
  PointIdsToEndNodes(vtkPointIdsToEndNodeMap()),
  EndNodesToArcs(vtkCmbEndNodesToArcMap()),
  UseSnapping(false),
  SnapRadius(0),
  SnapRadiusSquared(0),
  LocatorNeedsRebuilding(true)
{
}

//-----------------------------------------------------------------------------
vtkCMBArcManager::~vtkCMBArcManager()
{
  if ( this->PointLocator )
    {
    this->PointLocator->Delete();
    }

  //If the manager being deleted isn't the static manager
  //it means it was only working on a subset of arcs and shouldn't delete
  //anything as it can only query not add, remove or modify
  if ( this != this->Instance )
    {
    return;
    }


  vtkCMBArcMap::iterator ait;
  while (this->Arcs.size() > 0)
    {
    ait = this->Arcs.begin();
    vtkCMBArc *arc = ait->second;
    arc->Delete();
    }

  vtkCMBArcSet::iterator asit;
  while (this->UndoArcs.size() > 0)
    {
    asit = this->UndoArcs.begin();
    vtkCMBArc *arc = (*asit);
    arc->Delete();
    }

  //make sure we delete the arcs before the end nodes
  //that way the arc destructor can properly query if we have a reference
  //to the end node
  vtkCmbEndNodesToArcMap::iterator it;
  while (this->EndNodesToArcs.size() > 0)
    {
    it = this->EndNodesToArcs.begin();
    vtkCMBArcEndNode *endNode = it->first;
    this->EndNodesToArcs.erase(it);
    delete endNode;
    }
}

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

  os << indent << "vtkCMBArcManager Single instance = "
     << static_cast<void*>(vtkCMBArcManager::Instance) << endl;
}

//-----------------------------------------------------------------------------
// Up the reference count so it behaves like New
vtkCMBArcManager* vtkCMBArcManager::New()
{
  vtkCMBArcManager* ret = vtkCMBArcManager::GetInstance();
  ret->Register(NULL);
  return ret;
}

//-----------------------------------------------------------------------------
// Return the single instance of the vtkCMBArcManager
vtkCMBArcManager* vtkCMBArcManager::GetInstance()
{
  if(!vtkCMBArcManager::Instance)
    {
    // Try the factory first
    vtkCMBArcManager::Instance = static_cast<vtkCMBArcManager*>
      (vtkObjectFactory::CreateInstance("vtkCMBArcManager"));
    // if the factory did not provide one, then create it here
    if(!vtkCMBArcManager::Instance)
      {
      // if the factory failed to create the object,
      // then destroy it now, as vtkDebugLeaks::ConstructClass was called
      // with "vtkCMBArcManager", and not the real name of the class
      vtkCMBArcManager::Instance = new vtkCMBArcManager;
      }
    }
  // return the instance
  return vtkCMBArcManager::Instance;
}

//-----------------------------------------------------------------------------
void vtkCMBArcManager::SetInstance(vtkCMBArcManager* instance)
{
  if (vtkCMBArcManager::Instance==instance)
    {
    return;
    }
  // preferably this will be NULL
  if (vtkCMBArcManager::Instance)
    {
    vtkCMBArcManager::Instance->Delete();
    }
  vtkCMBArcManager::Instance = instance;
  if (!instance)
    {
    return;
    }
  // user will call ->Delete() after setting instance
  instance->Register(NULL);
}

//-----------------------------------------------------------------------------
int vtkCMBArcManager::GetNumberOfArcs() const
{
  return static_cast<int>(this->Arcs.size());
}

//-----------------------------------------------------------------------------
int vtkCMBArcManager::GetNumberOfEndNodes() const
{
  return static_cast<int>(this->EndNodesToArcs.size());
}

//-----------------------------------------------------------------------------
vtkCMBArc* vtkCMBArcManager::GetArc(const vtkIdType& id)
{
  vtkCMBArcMap::const_iterator it = this->Arcs.find(id);
  if ( it == this->Arcs.end() )
    {
    return NULL;
    }
  return it->second;
}

//-----------------------------------------------------------------------------
vtkCMBArc* vtkCMBArcManager::GetArcReadyForDeletion(const vtkIdType& id)
{
  //this is slower since we don't index the arcs on id
  vtkCMBArcSet::const_iterator asit;
  for (asit = this->UndoArcs.begin(); asit != this->UndoArcs.end(); ++asit)
    {
    if ( (*asit)->GetId() == id )
      {
      return (*asit);
      }
    }
  return NULL;
}

//-----------------------------------------------------------------------------
void vtkCMBArcManager::SetSnapRadius(double radius)
{
  if ( radius >= 0 )
    {
    this->SnapRadius = radius;
    this->SnapRadiusSquared = radius * radius;
    }
}

//-----------------------------------------------------------------------------
bool vtkCMBArcManager::IsManagedEndNode(vtkCMBArcEndNode *endNode) const
{
  return (this->EndNodesToArcs.find(endNode) != this->EndNodesToArcs.end());
}

//-----------------------------------------------------------------------------
int vtkCMBArcManager::GetNumberOfArcs(vtkCMBArcEndNode *endNode) const
{
  if (!this->IsManagedEndNode(endNode))
    {
    //error we are not managing this end node
    return -1;
    }
  return static_cast<int>(this->EndNodesToArcs.find(endNode)->second.size());
}

//-----------------------------------------------------------------------------
std::set<vtkCMBArc*> vtkCMBArcManager::GetConnectedArcs(
                                        vtkCMBArcEndNode *endNode)
{
  if (!this->IsManagedEndNode(endNode))
    {
    //error we are not managing this end node
    return std::set<vtkCMBArc*>();
    }
  vtkCmbEndNodesToArcMap::const_iterator it = this->EndNodesToArcs.find(endNode);
  return it->second;
}

//-----------------------------------------------------------------------------
std::set<vtkCMBArc*> vtkCMBArcManager::GetConnectedArcs(vtkCMBArc* arc)
{
  if (!arc)
    {
    return std::set<vtkCMBArc*>();
    }

  int numEndNodes = arc->GetNumberOfEndNodes();
  if ( numEndNodes == 1 )
    {
    vtkCMBArcSet set = this->GetConnectedArcs(arc->GetEndNode(0));
    set.erase(arc);
    return set;
    }
  else if ( numEndNodes == 2)
    {
    //use a set to remove any duplicates arcs,
    //which happens when this is half of a loop
    vtkCMBArcSet combinedSet;
    vtkCMBArcSet set1 = this->EndNodesToArcs.find(arc->GetEndNode(0))->second;
    vtkCMBArcSet set2 = this->EndNodesToArcs.find(arc->GetEndNode(1))->second;
    std::set_union( set1.begin(),set1.end(),
                    set2.begin(),set2.end(),
                    std::inserter(combinedSet,combinedSet.begin()));
    combinedSet.erase(arc);
    return combinedSet;
    }
  else
    {
    return std::set<vtkCMBArc*>();
    }
}


//-----------------------------------------------------------------------------
vtkCMBArcEndNode* vtkCMBArcManager::GetEndNodeAt(double position[3])
{
  this->BuildLocator(); //make sure we have a locator
  vtkIdType pointId = -1;
  if ( this->UseSnapping && this->PointLocator->GetNumberOfPoints() > 0 )
    {
    double dist;
    pointId = this->PointLocator->FindClosestPointWithinSquaredRadius(
                  this->SnapRadiusSquared,position,dist);
    }
  else
    {
    pointId = this->PointLocator->IsInsertedPoint(position);
    }

  if (pointId == -1)
    {
    return NULL;
    }

  //get the end node for this id
  return this->EndNodeFromPointId(pointId);
}

//-----------------------------------------------------------------------------
vtkCMBArcEndNode* vtkCMBArcManager::CreateEndNode(double position[3])
{
  vtkCMBArcEndNode *preExistingEndNode = this->GetEndNodeAt(position);
  if (preExistingEndNode)
    {
    //the end node already exists in the system, so return the current one
    //rather than create a new one
    return preExistingEndNode;
    }

  //we have to create a new end node.
  return new vtkCMBArcEndNode(position);
}


//-----------------------------------------------------------------------------
vtkCMBArcEndNode* vtkCMBArcManager::MergeEndNodes(vtkCMBArcEndNode* endNode1,
                                                  vtkCMBArcEndNode *endNode2)
{
  if ( endNode1 == endNode2 )
    {
    //can't merge yourself
    return NULL;
    }

  if (this->IsManagedEndNode(endNode1) &&
      this->IsManagedEndNode(endNode2))
    {
    //if both the arcs are being tracked by this manager, we need
    //to merge the arc sets
    vtkCMBArcSet combinedSet;
    std::set<vtkCMBArc*> arcs1 = this->GetConnectedArcs(endNode1);
    std::set<vtkCMBArc*> arcs2 = this->GetConnectedArcs(endNode2);


    //so now we have to union the arcs that use both end nodes for the new
    //end node arc set
    std::set_union( arcs1.begin(),arcs1.end(),
                    arcs2.begin(),arcs2.end(),
                    std::inserter(combinedSet,combinedSet.begin()));

    //update the end node to arc relationship
    this->EndNodesToArcs.find(endNode1)->second = combinedSet;

    //now we have to mark all these arcs modified so the will rerender properly
    for(std::set<vtkCMBArc*>::iterator arcIt = arcs2.begin();
        arcIt != arcs2.end(); arcIt++)
        {
        //go through and fix all the arcs to point to the correct end node
        (*arcIt)->ReplaceEndNode(endNode2,endNode1);
        }
    return endNode1;
    }

  //we can't merge this end nodes.
  return NULL;
}

//-----------------------------------------------------------------------------
vtkCMBArcEndNode* vtkCMBArcManager::MoveEndNode(vtkCMBArcEndNode* endNode,
                                                double position[3])
{
  //does a point already exist at the passed in position?
  vtkCMBArcEndNode *preExistingEndNode = this->GetEndNodeAt(position);
  if (preExistingEndNode && preExistingEndNode == endNode)
    {
    //we found ourselves??
    return preExistingEndNode;
    }
  else if (preExistingEndNode)
    {
    //the end node already exists in the system!
    this->MergeEndNodes(preExistingEndNode,endNode);
    return preExistingEndNode;
    }
  else if (!endNode)
    {
    return this->CreateEndNode(position);
    }
  else
    {
    endNode->SetPosition(position);
    if ( this->IsManagedEndNode(endNode) )
      {
      //by moving this end node we just invalidated the point locator
      this->LocatorModified();

      //tell all arcs that use this end node that they are modified since the
      //end node has been moved. This will make them rerender
      //update the end node to arc relationship
      std::set<vtkCMBArc*> arcs=this->EndNodesToArcs.find(endNode)->second;

      //now we have to mark all these arcs modified so the will rerender properly
      for(std::set<vtkCMBArc*>::iterator arcIt = arcs.begin();
          arcIt != arcs.end(); arcIt++)
          {
          (*arcIt)->Modified();
          }
      }
    return endNode;
    }
}

//-----------------------------------------------------------------------------
bool vtkCMBArcManager::RemoveEndNode(vtkCMBArcEndNode *en, vtkCMBArc *arc)
{
  if (!this->IsManagedEndNode(en))
    {
    return false;
    }
  if (arc == NULL)
    {
    return false;
    }

  vtkCmbEndNodesToArcMap::iterator it = this->EndNodesToArcs.find(en);
  it->second.erase(arc);
  if ( it->second.size() == 0 )
    {
    //if the end node has no arcs we need to delete it.
    this->EndNodesToArcs.erase(en);
    delete en;
    this->LocatorModified();
    }
  return true;
}

//-----------------------------------------------------------------------------
bool vtkCMBArcManager::AddEndNode(vtkCMBArcEndNode *en, vtkCMBArc *arc)
{
  if ( en == NULL || arc == NULL )
    {
    return false;
    }
  if (!this->IsManagedEndNode(en))
    {
    this->EndNodesToArcs.insert(
      std::pair<vtkCMBArcEndNode*,vtkCMBArcSet>(en,vtkCMBArcSet()));
    this->AddEndNodeToLocator(en);
    }
  this->EndNodesToArcs.find(en)->second.insert(arc);

  //Rebuild the locator every time a point is found
  //outside the bounding box of the locator
  //we rebuild the locator after the endnode is added
  //to EndNodeToArcs so the rebuilt is valid.
  this->BuildLocator();


  return true;
}

//-----------------------------------------------------------------------------
void vtkCMBArcManager::RegisterArc(vtkCMBArc* arc)
{
  if (arc)
    {
    this->Arcs.insert( std::pair<vtkIdType,vtkCMBArc*>(arc->GetId(),arc));
    }
}

//-----------------------------------------------------------------------------
void vtkCMBArcManager::UnRegisterArc(vtkCMBArc* arc)
{
  if (arc)
    {
    this->Arcs.erase(arc->GetId());

    //see if the arc is in the undo set
    if (this->UndoArcs.find(arc) != this->UndoArcs.end())
      {
      //this is needed because arcs that are in the undo set will call
      //unregister when they are fully removed from the system. But the
      //manager won't know it.
      this->UndoArcs.erase(arc);
      }
    }

}


//-----------------------------------------------------------------------------
void vtkCMBArcManager::MarkedForDeletion(vtkCMBArc* arc)
{
  if (arc)
    {
    this->UnRegisterArc(arc);
    this->UndoArcs.insert(arc);
    }
}

//-----------------------------------------------------------------------------
void vtkCMBArcManager::UnMarkedForDeletion(vtkCMBArc* arc)
{
  if (arc)
    {
    this->UndoArcs.erase(arc);
    this->RegisterArc(arc);
    }
}


//-----------------------------------------------------------------------------
vtkCMBArcEndNode* vtkCMBArcManager::EndNodeFromPointId(
                                                const vtkIdType &pointId)
{
  if ( pointId < 0 || pointId > static_cast<vtkIdType>(this->EndNodesToArcs.size()))
    {
    return NULL;
    }
  return this->PointIdsToEndNodes.find(pointId)->second;
}

//-----------------------------------------------------------------------------
void vtkCMBArcManager::AddEndNodeToLocator(vtkCMBArcEndNode *en)
{

  vtkIdType index = this->PointLocator->InsertNextPoint(en->GetPosition());
  this->PointIdsToEndNodes.insert(
      std::pair<vtkIdType,vtkCMBArcEndNode*>(index,en));

  //don't call build locator here, as we haven't created the bi-directional
  //map. Allow what ever is calling this method to call build locator, as
  //it will create the bi-directional map
  const double* pos = en->GetPosition();
  if(!this->LocatorBounds.ContainsPoint(pos[0],pos[1],pos[2]))
    {
    this->LocatorModified();
    }
  }


  //-----------------------------------------------------------------------------
  void vtkCMBArcManager::BuildLocator( )
  {
  if (!this->LocatorNeedsRebuilding)
    {
    return;
    }

  //clear out all the old info
  this->PointIdsToEndNodes.clear();
  if ( this->PointLocator )
    {
    this->PointLocator->Initialize();
    }
  else
    {
    this->PointLocator = vtkIncrementalOctreePointLocator::New();
    }

  double locatorBounds[6] = {-1.0,1.0,-1.0,1.0,-1.0,1.0};
  vtkNew<vtkPoints> points;
  points->SetDataTypeToDouble();
  if(this->EndNodesToArcs.size() > 0)
    {
    points->SetNumberOfPoints(this->EndNodesToArcs.size());
    vtkIdType index = 0;
    vtkCmbEndNodesToArcMap::const_iterator it;
    for (it = this->EndNodesToArcs.begin(); it != this->EndNodesToArcs.end();
         ++it, ++index)
      {
      //add each point and update the lookup from idtype to arc
      points->SetPoint(index,it->first->GetPosition());
      this->PointIdsToEndNodes.insert(
        std::pair<vtkIdType,vtkCMBArcEndNode*>(index,it->first));
      }
    points->GetBounds(locatorBounds);
    }

  //setup the new locator
  this->PointLocator->SetTolerance(0.0);
  this->PointLocator->SetBuildCubicOctree(1);
  this->PointLocator->SetMaxPointsPerLeaf(128);


  //we need to set the data set so that the point locator can properly
  //rebuild itself when needed for searching.
  vtkNew<vtkPolyData> data;
  data->SetPoints(points.GetPointer());
  this->PointLocator->SetDataSet(data.GetPointer());

  //we know have to to build the locator for searching and insertion
  //we need to call BuildLocator if the point set has values, since
  //it has the correct bounds. Otherwise we have to call InitPointInsertion
  //since the bounds that buildlocator has will be wrong.
  if(points->GetNumberOfPoints() > 0)
    {
    this->PointLocator->BuildLocator();
    }
  else
    {
    //use the custom default bounds that are not degenerate
    this->PointLocator->InitPointInsertion(points.GetPointer(),locatorBounds);
    }

  this->LocatorBounds.SetBounds(locatorBounds);
  this->LocatorNeedsRebuilding = false;
}

//-----------------------------------------------------------------------------
void vtkCMBArcManager::LocatorModified( )
{
  this->LocatorNeedsRebuilding = true;
}
