// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-FileCopyrightText: Copyright 2008 Sandia Corporation
// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-Sandia-USGov

#include "vtkMergeGraphs.h"

#include "vtkDataSetAttributes.h"
#include "vtkEdgeListIterator.h"
#include "vtkIdTypeArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMergeTables.h"
#include "vtkMutableDirectedGraph.h"
#include "vtkMutableGraphHelper.h"
#include "vtkMutableUndirectedGraph.h"
#include "vtkObjectFactory.h"
#include "vtkSmartPointer.h"
#include "vtkTable.h"

#include <map>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkMergeGraphs);
//------------------------------------------------------------------------------
vtkMergeGraphs::vtkMergeGraphs()
{
  this->SetNumberOfInputPorts(2);
  this->SetNumberOfOutputPorts(1);
  this->UseEdgeWindow = false;
  this->EdgeWindowArrayName = nullptr;
  this->SetEdgeWindowArrayName("time");
  this->EdgeWindow = 10000;
}

//------------------------------------------------------------------------------
vtkMergeGraphs::~vtkMergeGraphs()
{
  this->SetEdgeWindowArrayName(nullptr);
}

//------------------------------------------------------------------------------
int vtkMergeGraphs::FillInputPortInformation(int port, vtkInformation* info)
{
  if (port == 0)
  {
    info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkGraph");
  }
  else if (port == 1)
  {
    info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkGraph");
    info->Set(vtkAlgorithm::INPUT_IS_OPTIONAL(), 1);
  }

  return 1;
}

//------------------------------------------------------------------------------
// Fills array_map with matching arrays from data1 to data2
static void vtkMergeGraphsCreateArrayMapping(
  std::map<vtkAbstractArray*, vtkAbstractArray*>& array_map, vtkDataSetAttributes* data1,
  vtkDataSetAttributes* data2)
{
  vtkIdType narr1 = data1->GetNumberOfArrays();
  for (int arr1 = 0; arr1 < narr1; ++arr1)
  {
    vtkAbstractArray* a1 = data1->GetAbstractArray(arr1);
    vtkAbstractArray* a2 = data2->GetAbstractArray(a1->GetName());
    if (a2 && a1->GetDataType() == a2->GetDataType() &&
      a1->GetNumberOfComponents() == a2->GetNumberOfComponents())
    {
      array_map[a1] = a2;
    }
  }
  // Force pedigree id array to match
  if (data1->GetPedigreeIds() && data2->GetPedigreeIds())
    array_map[data1->GetPedigreeIds()] = data2->GetPedigreeIds();
}

//------------------------------------------------------------------------------
// Uses array_map to append a row to data1 corresponding to
// row index2 of mapped arrays (which came from data2)
static void vtkMergeGraphsAddRow(vtkDataSetAttributes* data1, vtkIdType index2,
  std::map<vtkAbstractArray*, vtkAbstractArray*>& array_map)
{
  int narr1 = data1->GetNumberOfArrays();
  for (int arr1 = 0; arr1 < narr1; ++arr1)
  {
    vtkAbstractArray* a1 = data1->GetAbstractArray(arr1);
    if (array_map.find(a1) != array_map.end())
    {
      vtkAbstractArray* a2 = array_map[a1];
      a1->InsertNextTuple(index2, a2);
    }
    else
    {
      vtkIdType num_values = a1->GetNumberOfTuples() * a1->GetNumberOfComponents();
      for (vtkIdType i = 0; i < a1->GetNumberOfComponents(); ++i)
      {
        a1->InsertVariantValue(num_values + i, vtkVariant());
      }
    }
  }
}

//------------------------------------------------------------------------------
int vtkMergeGraphs::RequestData(
  vtkInformation*, vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  vtkInformation* graph1_info = inputVector[0]->GetInformationObject(0);
  vtkGraph* graph1 = vtkGraph::SafeDownCast(graph1_info->Get(vtkDataObject::DATA_OBJECT()));

  // Copy structure into output graph.
  vtkInformation* outputInfo = outputVector->GetInformationObject(0);
  vtkGraph* output = vtkGraph::SafeDownCast(outputInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkInformation* graph2_info = inputVector[1]->GetInformationObject(0);
  if (!graph2_info)
  {
    // If no second graph provided, we're done
    output->CheckedShallowCopy(graph1);
    return 1;
  }

  vtkGraph* graph2 = vtkGraph::SafeDownCast(graph2_info->Get(vtkDataObject::DATA_OBJECT()));

  // Make a copy of the graph
  vtkSmartPointer<vtkMutableGraphHelper> builder = vtkSmartPointer<vtkMutableGraphHelper>::New();
  if (vtkDirectedGraph::SafeDownCast(output))
  {
    vtkSmartPointer<vtkMutableDirectedGraph> g = vtkSmartPointer<vtkMutableDirectedGraph>::New();
    builder->SetGraph(g);
  }
  else
  {
    vtkSmartPointer<vtkMutableUndirectedGraph> g =
      vtkSmartPointer<vtkMutableUndirectedGraph>::New();
    builder->SetGraph(g);
  }
  builder->GetGraph()->DeepCopy(graph1);

  if (!this->ExtendGraph(builder, graph2))
  {
    return 0;
  }

  if (!output->CheckedShallowCopy(builder->GetGraph()))
  {
    vtkErrorMacro("Output graph format invalid.");
    return 0;
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkMergeGraphs::ExtendGraph(vtkMutableGraphHelper* builder, vtkGraph* graph2)
{
  vtkAbstractArray* ped_ids1 = builder->GetGraph()->GetVertexData()->GetPedigreeIds();
  if (!ped_ids1)
  {
    vtkErrorMacro("First graph must have pedigree ids");
    return 0;
  }

  vtkAbstractArray* ped_ids2 = graph2->GetVertexData()->GetPedigreeIds();
  if (!ped_ids1)
  {
    vtkErrorMacro("Second graph must have pedigree ids");
    return 0;
  }

  // Find matching vertex arrays
  std::map<vtkAbstractArray*, vtkAbstractArray*> vert_array_map;
  vtkDataSetAttributes* vert_data1 = builder->GetGraph()->GetVertexData();
  vtkMergeGraphsCreateArrayMapping(vert_array_map, vert_data1, graph2->GetVertexData());

  // Find graph1 vertices matching graph2's pedigree ids
  vtkIdType n2 = graph2->GetNumberOfVertices();
  std::vector<vtkIdType> graph2_to_graph1(n2);
  for (vtkIdType vert2 = 0; vert2 < n2; ++vert2)
  {
    vtkIdType vert1 = ped_ids1->LookupValue(ped_ids2->GetVariantValue(vert2));
    if (vert1 == -1)
    {
      vert1 = builder->AddVertex();
      vtkMergeGraphsAddRow(vert_data1, vert2, vert_array_map);
    }
    graph2_to_graph1[vert2] = vert1;
  }

  // Find matching edge arrays
  std::map<vtkAbstractArray*, vtkAbstractArray*> edge_array_map;
  vtkDataSetAttributes* edge_data1 = builder->GetGraph()->GetEdgeData();
  vtkMergeGraphsCreateArrayMapping(edge_array_map, edge_data1, graph2->GetEdgeData());

  // For each edge in graph2, add it to the output
  vtkSmartPointer<vtkEdgeListIterator> it = vtkSmartPointer<vtkEdgeListIterator>::New();
  graph2->GetEdges(it);
  while (it->HasNext())
  {
    vtkEdgeType e = it->Next();
    vtkIdType source = graph2_to_graph1[e.Source];
    vtkIdType target = graph2_to_graph1[e.Target];
    if (source != -1 && target != -1)
    {
      builder->AddEdge(source, target);
      vtkMergeGraphsAddRow(edge_data1, e.Id, edge_array_map);
    }
  }

  // Remove edges if using an edge window.
  if (this->UseEdgeWindow)
  {
    if (!this->EdgeWindowArrayName)
    {
      vtkErrorMacro("EdgeWindowArrayName must not be null if using edge window.");
      return 0;
    }
    vtkDataArray* windowArr = vtkArrayDownCast<vtkDataArray>(
      builder->GetGraph()->GetEdgeData()->GetAbstractArray(this->EdgeWindowArrayName));
    if (!windowArr)
    {
      vtkErrorMacro("EdgeWindowArrayName not found or not a numeric array.");
      return 0;
    }
    double range[2];
    range[0] = VTK_DOUBLE_MAX;
    range[1] = VTK_DOUBLE_MIN;
    vtkIdType numEdges = builder->GetGraph()->GetNumberOfEdges();
    for (vtkIdType i = 0; i < numEdges; ++i)
    {
      double val = windowArr->GetTuple1(i);
      if (val < range[0])
      {
        range[0] = val;
      }
      if (val > range[1])
      {
        range[1] = val;
      }
    }
    double cutoff = range[1] - this->EdgeWindow;
    if (range[0] < cutoff)
    {
      vtkSmartPointer<vtkIdTypeArray> edgesToRemove = vtkSmartPointer<vtkIdTypeArray>::New();
      for (vtkIdType i = 0; i < numEdges; ++i)
      {
        if (windowArr->GetTuple1(i) < cutoff)
        {
          edgesToRemove->InsertNextValue(i);
        }
      }
      builder->RemoveEdges(edgesToRemove);
    }
  }

  return 1;
}

//------------------------------------------------------------------------------
void vtkMergeGraphs::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "UseEdgeWindow: " << this->UseEdgeWindow << endl;
  os << indent << "EdgeWindowArrayName: "
     << (this->EdgeWindowArrayName ? this->EdgeWindowArrayName : "(none)") << endl;
  os << indent << "EdgeWindow: " << this->EdgeWindow << endl;
}
VTK_ABI_NAMESPACE_END
