// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkAnimationScene.h"

#include "vtkCollection.h"
#include "vtkCollectionIterator.h"
#include "vtkCommand.h"
#include "vtkObjectFactory.h"
#include "vtkTimerLog.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkAnimationScene);

//------------------------------------------------------------------------------
vtkAnimationScene::vtkAnimationScene()
{
  this->PlayMode = PLAYMODE_SEQUENCE;
  this->FrameRate = 10.0;
  this->Loop = 0;
  this->InPlay = 0;
  this->StopPlay = 0;

  this->AnimationCues = vtkCollection::New();
  this->AnimationCuesIterator = this->AnimationCues->NewIterator();
  this->AnimationTimer = vtkTimerLog::New();
}

//------------------------------------------------------------------------------
vtkAnimationScene::~vtkAnimationScene()
{
  if (this->InPlay)
  {
    this->Stop();
  }
  this->AnimationCues->Delete();
  this->AnimationCuesIterator->Delete();
  this->AnimationTimer->Delete();
}

//------------------------------------------------------------------------------
void vtkAnimationScene::AddCue(vtkAnimationCue* cue)
{
  if (this->AnimationCues->IndexOfFirstOccurrence(cue) >= 0)
  {
    vtkErrorMacro("Animation cue already present in the scene");
    return;
  }
  if (this->TimeMode == vtkAnimationCue::TIMEMODE_NORMALIZED &&
    cue->GetTimeMode() != vtkAnimationCue::TIMEMODE_NORMALIZED)
  {
    vtkErrorMacro("A cue with relative time mode cannot be added to a scene "
                  "with normalized time mode.");
    return;
  }
  this->AnimationCues->AddItem(cue);
}

//------------------------------------------------------------------------------
void vtkAnimationScene::RemoveCue(vtkAnimationCue* cue)
{
  this->AnimationCues->RemoveItem(cue);
}

//------------------------------------------------------------------------------
void vtkAnimationScene::RemoveAllCues()
{
  this->AnimationCues->RemoveAllItems();
}
//------------------------------------------------------------------------------
int vtkAnimationScene::GetNumberOfCues()
{
  return this->AnimationCues->GetNumberOfItems();
}
//------------------------------------------------------------------------------
void vtkAnimationScene::SetTimeMode(int mode)
{
  if (mode == vtkAnimationCue::TIMEMODE_NORMALIZED)
  {
    // If normalized time mode is being set on the scene,
    // ensure that none of the contained cues need relative times.
    vtkCollectionIterator* it = this->AnimationCuesIterator;
    for (it->InitTraversal(); !it->IsDoneWithTraversal(); it->GoToNextItem())
    {
      vtkAnimationCue* cue = vtkAnimationCue::SafeDownCast(it->GetCurrentObject());
      if (cue && cue->GetTimeMode() != vtkAnimationCue::TIMEMODE_NORMALIZED)
      {
        vtkErrorMacro("Scene contains a cue in relative mode. It must be removed "
                      "or changed to normalized mode before changing the scene time mode");
        return;
      }
    }
  }
  this->Superclass::SetTimeMode(mode);
}

//------------------------------------------------------------------------------
void vtkAnimationScene::InitializeChildren()
{
  // run through all the cues and init them.
  vtkCollectionIterator* it = this->AnimationCuesIterator;
  for (it->InitTraversal(); !it->IsDoneWithTraversal(); it->GoToNextItem())
  {
    vtkAnimationCue* cue = vtkAnimationCue::SafeDownCast(it->GetCurrentObject());
    if (cue)
    {
      cue->Initialize();
    }
  }
}

//------------------------------------------------------------------------------
void vtkAnimationScene::FinalizeChildren()
{
  vtkCollectionIterator* it = this->AnimationCuesIterator;
  for (it->InitTraversal(); !it->IsDoneWithTraversal(); it->GoToNextItem())
  {
    vtkAnimationCue* cue = vtkAnimationCue::SafeDownCast(it->GetCurrentObject());
    if (cue)
    {
      cue->Finalize();
    }
  }
}

//------------------------------------------------------------------------------
void vtkAnimationScene::Play()
{
  if (this->InPlay)
  {
    return;
  }

  if (this->TimeMode == vtkAnimationCue::TIMEMODE_NORMALIZED)
  {
    vtkErrorMacro("Cannot play a scene with normalized time mode");
    return;
  }
  if (this->EndTime <= this->StartTime)
  {
    vtkErrorMacro("Scene start and end times are not suitable for playing");
    return;
  }

  this->InvokeEvent(vtkCommand::StartEvent);

  this->InPlay = 1;
  this->StopPlay = 0;
  this->FrameRate = (this->FrameRate == 0.0) ? 1.0 : this->FrameRate;
  // the actual play loop, check for StopPlay flag.

  double currenttime = this->AnimationTime;
  // adjust currenttime to a valid time.
  currenttime =
    (currenttime < this->StartTime || currenttime >= this->EndTime) ? this->StartTime : currenttime;

  double time_per_frame = (this->PlayMode == PLAYMODE_SEQUENCE) ? (1.0 / this->FrameRate) : 1;
  do
  {
    this->Initialize(); // Set the Scene in uninitialized mode.
    this->AnimationTimer->StartTimer();
    double timer_start_time = currenttime;
    double deltatime = 0.0;
    do
    {
      this->Tick(currenttime, deltatime, currenttime);

      // needed to compute delta times.
      double previous_tick_time = currenttime;

      switch (this->PlayMode)
      {
        case PLAYMODE_REALTIME:
          this->AnimationTimer->StopTimer();
          currenttime = this->AnimationTimer->GetElapsedTime() + timer_start_time;
          break;

        case PLAYMODE_SEQUENCE:
          currenttime += time_per_frame;
          break;

        default:
          vtkErrorMacro("Invalid Play Mode");
          this->StopPlay = 1;
      }

      deltatime = currenttime - previous_tick_time;
      deltatime = (deltatime < 0) ? -1 * deltatime : deltatime;
    } while (!this->StopPlay && this->CueState != vtkAnimationCue::INACTIVE);
    // End of loop for 1 cycle.

    // restart the loop.
    currenttime = this->StartTime;
  } while (this->Loop && !this->StopPlay);

  this->StopPlay = 0;
  this->InPlay = 0;

  this->InvokeEvent(vtkCommand::EndEvent);
}

//------------------------------------------------------------------------------
void vtkAnimationScene::Stop()
{
  if (!this->InPlay)
  {
    return;
  }
  this->StopPlay = 1;
}

//------------------------------------------------------------------------------
void vtkAnimationScene::TickInternal(double currenttime, double deltatime, double clocktime)
{
  this->AnimationTime = currenttime;
  this->ClockTime = clocktime;

  vtkCollectionIterator* iter = this->AnimationCuesIterator;
  for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
  {
    vtkAnimationCue* cue = vtkAnimationCue::SafeDownCast(iter->GetCurrentObject());
    if (cue)
    {
      const PlayDirection dir = cue->GetDirection(); // back up cue's direction.
      cue->SetDirection(this->Direction);
      switch (cue->GetTimeMode())
      {
        case vtkAnimationCue::TIMEMODE_RELATIVE:
          cue->Tick(currenttime - this->StartTime, deltatime, clocktime);
          break;

        case vtkAnimationCue::TIMEMODE_NORMALIZED:
          cue->Tick((currenttime - this->StartTime) / (this->EndTime - this->StartTime),
            deltatime / (this->EndTime - this->StartTime), clocktime);
          break;

        default:
          vtkErrorMacro("Invalid cue time mode");
      }
      cue->SetDirection(dir); // restore old direction.
    }
  }

  this->Superclass::TickInternal(currenttime, deltatime, clocktime);
}

//------------------------------------------------------------------------------
void vtkAnimationScene::StartCueInternal()
{
  this->Superclass::StartCueInternal();
  this->InitializeChildren();
}

//------------------------------------------------------------------------------
void vtkAnimationScene::EndCueInternal()
{
  this->FinalizeChildren();
  this->Superclass::EndCueInternal();
}

//------------------------------------------------------------------------------
void vtkAnimationScene::SetAnimationTime(double currenttime)
{
  if (this->InPlay)
  {
    vtkErrorMacro("SetAnimationTime cannot be called while playing");
    return;
  }
  this->Initialize();
  this->Tick(currenttime, 0.0, currenttime);
  if (this->CueState == vtkAnimationCue::INACTIVE)
  {
    this->Finalize();
  }
}

//------------------------------------------------------------------------------
void vtkAnimationScene::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "PlayMode: " << this->PlayMode << endl;
  os << indent << "FrameRate: " << this->FrameRate << endl;
  os << indent << "Loop: " << this->Loop << endl;
  os << indent << "InPlay: " << this->InPlay << endl;
  os << indent << "StopPlay: " << this->StopPlay << endl;
}
VTK_ABI_NAMESPACE_END
