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

#include "vtkCommand.h"
#include "vtkGarbageCollector.h"
#include "vtkImageData.h"
#include "vtkImageDilateErode3D.h"
#include "vtkObjectFactory.h"

#include "vtkExecutive.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"

#include <cmath>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkImageOpenClose3D);

//------------------------------------------------------------------------------
// functions to convert progress calls.
class vtkImageOpenClose3DProgress : public vtkCommand
{
public:
  // generic new method
  static vtkImageOpenClose3DProgress* New() { return new vtkImageOpenClose3DProgress; }

  // the execute
  void Execute(vtkObject* caller, unsigned long event, void* vtkNotUsed(v)) override
  {
    vtkAlgorithm* alg = vtkAlgorithm::SafeDownCast(caller);
    if (event == vtkCommand::ProgressEvent && alg)
    {
      this->Self->UpdateProgress(this->Offset + 0.5 * alg->GetProgress());
    }
  }

  // some ivars that should be set
  vtkImageOpenClose3D* Self;
  double Offset;
};

//------------------------------------------------------------------------------
vtkImageOpenClose3D::vtkImageOpenClose3D()
{
  // create the filter chain
  this->Filter0 = vtkImageDilateErode3D::New();
  vtkImageOpenClose3DProgress* cb = vtkImageOpenClose3DProgress::New();
  cb->Self = this;
  cb->Offset = 0;
  this->Filter0->AddObserver(vtkCommand::ProgressEvent, cb);
  cb->Delete();

  this->Filter1 = vtkImageDilateErode3D::New();
  cb = vtkImageOpenClose3DProgress::New();
  cb->Self = this;
  cb->Offset = 0.5;
  this->Filter1->AddObserver(vtkCommand::ProgressEvent, cb);
  cb->Delete();
  this->SetOpenValue(0.0);
  this->SetCloseValue(255.0);

  // connect up the internal pipeline
  this->Filter1->SetInputConnection(this->Filter0->GetOutputPort());
}

//------------------------------------------------------------------------------
// Destructor: Delete the sub filters.
vtkImageOpenClose3D::~vtkImageOpenClose3D()
{
  if (this->Filter0)
  {
    this->Filter0->Delete();
  }

  if (this->Filter1)
  {
    this->Filter1->Delete();
  }
}

//------------------------------------------------------------------------------
void vtkImageOpenClose3D::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Filter0: \n";
  this->Filter0->PrintSelf(os, indent.GetNextIndent());
  os << indent << "Filter1: \n";
  this->Filter1->PrintSelf(os, indent.GetNextIndent());
}

//------------------------------------------------------------------------------
// Turn debugging output on. (in sub filters also)
void vtkImageOpenClose3D::DebugOn()
{
  this->vtkObject::DebugOn();
  if (this->Filter0)
  {
    this->Filter0->DebugOn();
  }
  if (this->Filter1)
  {
    this->Filter1->DebugOn();
  }
}

//------------------------------------------------------------------------------
void vtkImageOpenClose3D::DebugOff()
{
  this->vtkObject::DebugOff();
  if (this->Filter0)
  {
    this->Filter0->DebugOff();
  }
  if (this->Filter1)
  {
    this->Filter1->DebugOff();
  }
}

//------------------------------------------------------------------------------
// Pass modified message to sub filters.
void vtkImageOpenClose3D::Modified()
{
  this->vtkObject::Modified();
  if (this->Filter0)
  {
    this->Filter0->Modified();
  }

  if (this->Filter1)
  {
    this->Filter1->Modified();
  }
}

//------------------------------------------------------------------------------
// This method considers the sub filters MTimes when computing this objects
// MTime
vtkMTimeType vtkImageOpenClose3D::GetMTime()
{
  vtkMTimeType t1, t2;

  t1 = this->Superclass::GetMTime();
  if (this->Filter0)
  {
    t2 = this->Filter0->GetMTime();
    if (t2 > t1)
    {
      t1 = t2;
    }
  }
  if (this->Filter1)
  {
    t2 = this->Filter1->GetMTime();
    if (t2 > t1)
    {
      t1 = t2;
    }
  }

  return t1;
}

//------------------------------------------------------------------------------
int vtkImageOpenClose3D::ComputePipelineMTime(vtkInformation* request,
  vtkInformationVector** inInfoVec, vtkInformationVector* outInfoVec, int requestFromOutputPort,
  vtkMTimeType* mtime)
{
  // Process the request on the internal pipeline.  Share our input
  // information with the first filter and our output information with
  // the last filter.
  vtkExecutive* exec0 = this->Filter0->GetExecutive();
  vtkExecutive* exec1 = this->Filter1->GetExecutive();
  exec0->SetSharedInputInformation(inInfoVec);
  exec1->SetSharedOutputInformation(outInfoVec);
  vtkMTimeType mtime1;
  if (exec1->ComputePipelineMTime(request, exec1->GetInputInformation(),
        exec1->GetOutputInformation(), requestFromOutputPort, &mtime1))
  {
    // Now run the request in this algorithm.
    return this->Superclass::ComputePipelineMTime(
      request, inInfoVec, outInfoVec, requestFromOutputPort, mtime);
  }
  else
  {
    // The internal pipeline failed to process the request.
    vtkErrorMacro("Internal pipeline failed to process pipeline modified "
                  "time request.");
    return 0;
  }
}

//------------------------------------------------------------------------------
vtkTypeBool vtkImageOpenClose3D::ProcessRequest(
  vtkInformation* request, vtkInformationVector** inInfoVec, vtkInformationVector* outInfoVec)
{
  // Process the request on the internal pipeline.  Share our input
  // information with the first filter and our output information with
  // the last filter.
  vtkExecutive* exec0 = this->Filter0->GetExecutive();
  vtkExecutive* exec1 = this->Filter1->GetExecutive();
  exec0->SetSharedInputInformation(inInfoVec);
  exec1->SetSharedOutputInformation(outInfoVec);
  return exec1->ProcessRequest(
    request, exec1->GetInputInformation(), exec1->GetOutputInformation());
}

//------------------------------------------------------------------------------
// Selects the size of gaps or objects removed.
void vtkImageOpenClose3D::SetKernelSize(int size0, int size1, int size2)
{
  if (!this->Filter0 || !this->Filter1)
  {
    vtkErrorMacro(<< "SetKernelSize: Sub filter not created yet.");
    return;
  }

  this->Filter0->SetKernelSize(size0, size1, size2);
  this->Filter1->SetKernelSize(size0, size1, size2);
  // Sub filters take care of modified.
}

//------------------------------------------------------------------------------
// Determines the value that will closed.
// Close value is first dilated, and then eroded
void vtkImageOpenClose3D::SetCloseValue(double value)
{
  if (!this->Filter0 || !this->Filter1)
  {
    vtkErrorMacro(<< "SetCloseValue: Sub filter not created yet.");
    return;
  }

  this->Filter0->SetDilateValue(value);
  this->Filter1->SetErodeValue(value);
}

//------------------------------------------------------------------------------
double vtkImageOpenClose3D::GetCloseValue()
{
  if (!this->Filter0)
  {
    vtkErrorMacro(<< "GetCloseValue: Sub filter not created yet.");
    return 0.0;
  }

  return this->Filter0->GetDilateValue();
}

//------------------------------------------------------------------------------
// Determines the value that will opened.
// Open value is first eroded, and then dilated.
void vtkImageOpenClose3D::SetOpenValue(double value)
{
  if (!this->Filter0 || !this->Filter1)
  {
    vtkErrorMacro(<< "SetOpenValue: Sub filter not created yet.");
    return;
  }

  this->Filter0->SetErodeValue(value);
  this->Filter1->SetDilateValue(value);
}

//------------------------------------------------------------------------------
double vtkImageOpenClose3D::GetOpenValue()
{
  if (!this->Filter0)
  {
    vtkErrorMacro(<< "GetOpenValue: Sub filter not created yet.");
    return 0.0;
  }

  return this->Filter0->GetErodeValue();
}

//------------------------------------------------------------------------------
void vtkImageOpenClose3D::ReportReferences(vtkGarbageCollector* collector)
{
  this->Superclass::ReportReferences(collector);
  // These filters share our input and are therefore involved in a
  // reference loop.
  vtkGarbageCollectorReport(collector, this->Filter0, "Filter0");
  vtkGarbageCollectorReport(collector, this->Filter1, "Filter1");
}
VTK_ABI_NAMESPACE_END
