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

  Program:   Visualization Toolkit
  Module:    vtkGarbageCollector.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 "vtkGarbageCollector.h"

#include <vtkstd/set>
#include <vtkstd/queue>

vtkCxxRevisionMacro(vtkGarbageCollector, "1.7");

//----------------------------------------------------------------------------
class vtkGarbageCollectorQueue: public vtkstd::queue<vtkObjectBase*> {};
class vtkGarbageCollectorQueued: public vtkstd::set<vtkObjectBase*> {};

//----------------------------------------------------------------------------
vtkGarbageCollector::vtkGarbageCollector(vtkGarbageCollectorQueue* q,
                                         vtkGarbageCollectorQueued* qd)
{
  this->Queue = q;
  this->Queued = qd;
  this->NetCount = 0;
  this->Current = 0;
}

//----------------------------------------------------------------------------
vtkGarbageCollector::~vtkGarbageCollector()
{
  this->SetReferenceCount(0);
}

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

//----------------------------------------------------------------------------
void vtkGarbageCollector::Register(vtkObjectBase*)
{
}

//----------------------------------------------------------------------------
void vtkGarbageCollector::UnRegister(vtkObjectBase*)
{
}

//----------------------------------------------------------------------------
void vtkGarbageCollector::Check(vtkObjectBase* root)
{
  // Allocate as much as possible on the stack.  This code runs every
  // time UnRegister is called on an object supporting garbage
  // collection.  It may be worth modifying the queue and set to use a
  // local array for storage until the size grows beyond some
  // constant.
  vtkGarbageCollectorQueue objectQueue;
  vtkGarbageCollectorQueued objectQueued;
  vtkGarbageCollector collector(&objectQueue, &objectQueued);

  // Uncomment these lines to get reference loop debugging for objects
  // with Debug flag set:
  //if(vtkObject* obj = vtkObject::SafeDownCast(root))
  //  {
  //  collector.SetDebug(obj->GetDebug());
  //  }

  collector.CheckReferenceLoops(root);
  collector.SetDebug(0);
}

//----------------------------------------------------------------------------
void vtkGarbageCollector::CheckReferenceLoops(vtkObjectBase* root)
{
  // This performs a BFS traversal of the reference graph associated
  // with the given root object.  If the total reference count of the
  // connected component is 0 when not counting internal references,
  // the entire component is deleted.

  // Fake an internal reference to the root to initialize the queue.
  this->Queued->clear();
  this->NetCount = 1;
  this->ReportReference(root, "");

  vtkDebugMacro("Starting reference graph walk with root "
                << root->GetClassName() << "(" << root << ")");

  // Loop until the queue is empty.
  while(!this->Queue->empty())
    {
    // Get the next object from the queue.
    this->Current = this->Queue->front();
    this->Queue->pop();

    vtkDebugMacro("Requesting references from "
                  << this->Current->GetClassName() << "("
                  << this->Current << ") with reference count "
                  << this->Current->GetReferenceCount());

    // Tell the object to report its references to us.
    this->Current->ReportReferences(this);
    }
  this->Current = 0;

  vtkDebugMacro("Finished reference graph walk with net reference count "
                << this->NetCount);

  // If the net reference count is 0, delete the entire connected
  // component of the reference graph.
  if(this->NetCount == 0)
    {
    // Report the connected component to be deleted.
    vtkstd::set<vtkObjectBase*>::iterator obj;
#ifndef VTK_LEAN_AND_MEAN
    if(this->Debug && vtkObject::GetGlobalWarningDisplay())
      {
      ostrstream msg;
      msg << "Deleting connected component of reference graph:\n";
      for(obj = this->Queued->begin(); obj != this->Queued->end(); ++obj)
        {
        msg << "  " << (*obj)->GetClassName() << "(" << *obj << ")\n";
        }
      msg << ends;
      vtkDebugMacro(<< msg.str());
      msg.rdbuf()->freeze(0);
      }
#endif

    // Notify all objects they are about to be garbage collected.
    // They will disable reference loop checking.
    for(obj = this->Queued->begin(); obj != this->Queued->end(); ++obj)
      {
      (*obj)->GarbageCollectionStarting();
      }

    // Disconnect the reference graph.
    for(obj = this->Queued->begin(); obj != this->Queued->end(); ++obj)
      {
      (*obj)->RemoveReferences();
      }

    // Notify all objects they have been garbage collected.  They will
    // delete themselves.
    for(obj = this->Queued->begin(); obj != this->Queued->end(); ++obj)
      {
      (*obj)->GarbageCollectionFinishing();
      }
    }
}

//----------------------------------------------------------------------------
#ifndef VTK_LEAN_AND_MEAN
void vtkGarbageCollector::ReportReference(vtkObjectBase* obj, const char* desc)
#else
void vtkGarbageCollector::ReportReference(vtkObjectBase* obj, const char*)
#endif
{
  if(obj)
    {
#ifndef VTK_LEAN_AND_MEAN
    if(this->Current && this->Debug && vtkObject::GetGlobalWarningDisplay())
      {
      ostrstream msg;
      msg << "ReportReference: "
          << this->Current->GetClassName() << "(" << this->Current << ") "
          << (desc?desc:"")
          << " -> " << obj->GetClassName() << "(" << obj << ")";
      msg << ends;
      vtkDebugMacro(<< msg.str());
      msg.rdbuf()->freeze(0);
      }
#endif

    // Add the object to the queue if it has not already been added.
    if(this->Queued->find(obj) == this->Queued->end())
      {
      // Include the references to the object.
      this->NetCount += obj->GetReferenceCount();

      // Queue the object.
      this->Queued->insert(obj);
      this->Queue->push(obj);
      }

    // This is an internal reference.
    --this->NetCount;
    }
}
