// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkStreaklineFilter.h"
#include "vtkCell.h"
#include "vtkCellArray.h"
#include "vtkFloatArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkIntArray.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkSetGet.h"
#include "vtkSmartPointer.h"

#include <algorithm>
#include <cassert>
#include <vector>

#define DEBUGSTREAKLINEFILTER 1
#ifdef DEBUGSTREAKLINEFILTER
#define Assert(a) assert(a)
#else
#define Assert(a)
#endif

VTK_ABI_NAMESPACE_BEGIN
class StreakParticle
{
public:
  vtkIdType Id;
  float Age;

  StreakParticle(vtkIdType id, float age)
    : Id(id)
    , Age(age)
  {
  }

  bool operator<(const StreakParticle& other) const { return this->Age > other.Age; }
};

typedef std::vector<StreakParticle> Streak;

vtkObjectFactoryNewMacro(vtkStreaklineFilter);

void StreaklineFilterInternal::Initialize(vtkParticleTracerBase* filter)
{
  this->Filter = filter;
  this->Filter->ForceReinjectionEveryNSteps = 1;
  this->Filter->IgnorePipelineTime = 1;
}

int StreaklineFilterInternal::OutputParticles(vtkPolyData* particles)
{
  this->Filter->Output = particles;

  return 1;
}

void StreaklineFilterInternal::Finalize()
{
  vtkPoints* points = this->Filter->Output->GetPoints();
  if (!points)
  {
    return;
  }

  vtkPointData* pd = this->Filter->Output->GetPointData();
  Assert(pd);
  vtkFloatArray* particleAge = vtkArrayDownCast<vtkFloatArray>(pd->GetArray("ParticleAge"));
  Assert(particleAge);
  vtkIntArray* seedIds = vtkArrayDownCast<vtkIntArray>(pd->GetArray("InjectedPointId"));
  Assert(seedIds);

  if (seedIds)
  {
    std::vector<Streak> streaks; // the streak lines in the current time step
    for (vtkIdType i = 0; i < points->GetNumberOfPoints(); i++)
    {
      int streakId = seedIds->GetValue(i);
      for (int j = static_cast<int>(streaks.size()); j <= streakId; j++)
      {
        streaks.emplace_back();
      }
      Streak& streak = streaks[streakId];
      float age = particleAge->GetValue(i);
      streak.emplace_back(i, age);
    }

    // sort streaks based on age
    for (unsigned int i = 0; i < streaks.size(); i++)
    {
      Streak& streak(streaks[i]);
      std::sort(streak.begin(), streak.end());
    }

    this->Filter->Output->SetLines(vtkSmartPointer<vtkCellArray>::New());
    this->Filter->Output->SetVerts(nullptr);
    vtkCellArray* outLines = this->Filter->Output->GetLines();
    Assert(outLines->GetNumberOfCells() == 0);
    Assert(outLines);
    for (unsigned int i = 0; i < streaks.size(); i++)
    {
      const Streak& streak(streaks[i]);
      vtkNew<vtkIdList> ids;

      for (unsigned int j = 0; j < streak.size(); j++)
      {
        Assert(j == 0 || streak[j].Age <= streak[j - 1].Age);
        if (j == 0 || streak[j].Age < streak[j - 1].Age)
        {
          ids->InsertNextId(streak[j].Id);
        }
      }
      if (ids->GetNumberOfIds() > 1)
      {
        outLines->InsertNextCell(ids);
      }
    }
  }
}

vtkStreaklineFilter::vtkStreaklineFilter()
{
  this->It.Initialize(this);
}

int vtkStreaklineFilter::OutputParticles(vtkPolyData* particles)
{
  return this->It.OutputParticles(particles);
}

void vtkStreaklineFilter::Finalize()
{
  return this->It.Finalize();
}

void vtkStreaklineFilter::PrintSelf(ostream& os, vtkIndent indent)
{
  Superclass::PrintSelf(os, indent);
}
VTK_ABI_NAMESPACE_END
