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

  Program:   ParaView
  Module:    vtkUndoSet.cxx

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html 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 "vtkUndoSet.h"

#include "vtkCollection.h"
#include "vtkCollectionIterator.h"
#include "vtkObjectFactory.h"
#include "vtkPVXMLElement.h"
#include "vtkUndoElement.h"

vtkStandardNewMacro(vtkUndoSet);
//-----------------------------------------------------------------------------
vtkUndoSet::vtkUndoSet()
{
  this->Collection = vtkCollection::New();
  this->TmpWorkingCollection = vtkCollection::New();
}

//-----------------------------------------------------------------------------
vtkUndoSet::~vtkUndoSet()
{
  this->Collection->Delete();
  this->TmpWorkingCollection->Delete();
}

//-----------------------------------------------------------------------------
int vtkUndoSet::AddElement(vtkUndoElement* elem)
{
  int num_elements = this->Collection->GetNumberOfItems();
  if (elem->GetMergeable() && num_elements > 0)
  {
    vtkUndoElement* prev =
      vtkUndoElement::SafeDownCast(this->Collection->GetItemAsObject(num_elements - 1));
    if (prev && prev->GetMergeable())
    {
      if (prev->Merge(elem))
      {
        // merge was successful, return index of the merge.
        return (num_elements - 1);
      }
    }
  }

  this->Collection->AddItem(elem);
  return num_elements;
}

//-----------------------------------------------------------------------------
void vtkUndoSet::RemoveElement(int index)
{
  this->Collection->RemoveItem(index);
}

//-----------------------------------------------------------------------------
vtkUndoElement* vtkUndoSet::GetElement(int index)
{
  return vtkUndoElement::SafeDownCast(this->Collection->GetItemAsObject(index));
}

//-----------------------------------------------------------------------------
void vtkUndoSet::RemoveAllElements()
{
  this->Collection->RemoveAllItems();
}

//-----------------------------------------------------------------------------
int vtkUndoSet::GetNumberOfElements()
{
  return this->Collection->GetNumberOfItems();
}

//-----------------------------------------------------------------------------
int vtkUndoSet::Redo()
{
  int max = this->Collection->GetNumberOfItems();
  for (int cc = 0; cc < max; cc++)
  {
    vtkUndoElement* elem = vtkUndoElement::SafeDownCast(this->Collection->GetItemAsObject(cc));

    // Init working context
    elem->SetUndoSetWorkingContext(this->TmpWorkingCollection);
    if (!elem->Redo())
    {
      vtkDebugMacro("Redo Action is failing. Start redoing the actions.");
      // redo failed, undo the half redone operations.
      for (int rr = cc - 1; rr >= 0; --rr)
      {
        vtkUndoElement* elemU = vtkUndoElement::SafeDownCast(this->Collection->GetItemAsObject(rr));
        elemU->SetUndoSetWorkingContext(this->TmpWorkingCollection); // Init
        elemU->Undo();
        elemU->SetUndoSetWorkingContext(nullptr); // Clear Working context
      }
      // Release ref of tmp objects
      this->TmpWorkingCollection->RemoveAllItems();
      return 0;
    }
    elem->SetUndoSetWorkingContext(nullptr); // Clear Working context
  }
  // Release ref of tmp objects
  this->TmpWorkingCollection->RemoveAllItems();
  return 1;
}

//-----------------------------------------------------------------------------
int vtkUndoSet::Undo()
{
  int max = this->Collection->GetNumberOfItems();
  for (int cc = max - 1; cc >= 0; --cc)
  {
    vtkUndoElement* elem = vtkUndoElement::SafeDownCast(this->Collection->GetItemAsObject(cc));

    // Init working context
    elem->SetUndoSetWorkingContext(this->TmpWorkingCollection);
    if (!elem->Undo())
    {
      vtkDebugMacro("Undo Action is failing. Start redoing the actions.");
      // undo failed, redo the half undone operations.
      for (int rr = 0; rr < cc; ++rr)
      {
        vtkUndoElement* elemR = vtkUndoElement::SafeDownCast(this->Collection->GetItemAsObject(rr));
        elemR->SetUndoSetWorkingContext(this->TmpWorkingCollection); // Init
        elemR->Redo();
        elemR->SetUndoSetWorkingContext(nullptr); // Clear Working context
      }
      // Release ref of tmp objects
      this->TmpWorkingCollection->RemoveAllItems();
      return 0;
    }
    elem->SetUndoSetWorkingContext(nullptr); // Clear Working context
  }
  // Release ref of tmp objects
  this->TmpWorkingCollection->RemoveAllItems();
  return 1;
}

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