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

#include "vtkAdjacentVertexIterator.h"
#include "vtkDataSetAttributes.h"
#include "vtkDoubleArray.h"
#include "vtkGraph.h"
#include "vtkIdTypeArray.h"
#include "vtkInEdgeIterator.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkIntArray.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkSelection.h"
#include "vtkSmartPointer.h"

#include <iostream>

#if defined(_MSC_VER)
#define _USE_MATH_DEFINES
#endif
#include <cstdlib>

using std::endl;

#define VTK_CREATE(type, name) vtkSmartPointer<type> name = vtkSmartPointer<type>::New()

///////////////////////////////////////////////////////////////////////////////////
// vtkKCoreLayout
VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkKCoreLayout);

// Default Constructor
vtkKCoreLayout::vtkKCoreLayout()
{
  this->SetNumberOfInputPorts(1);
  this->KCoreLabelArrayName = nullptr;
  this->PolarCoordsRadiusArrayName = nullptr;
  this->PolarCoordsAngleArrayName = nullptr;
  this->CartesianCoordsXArrayName = nullptr;
  this->CartesianCoordsYArrayName = nullptr;
  this->Cartesian = true;
  this->Polar = false;
  this->Epsilon = 0.2f;
  this->UnitRadius = 1.0f;
}

// Default Destructor
vtkKCoreLayout::~vtkKCoreLayout()
{
  this->KCoreLabelArrayName = nullptr;
}

// -------------------------------------------------------------------------------------------------
void vtkKCoreLayout::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  if (this->KCoreLabelArrayName)
  {
    os << indent << "KCoreLabelArrayName : " << this->KCoreLabelArrayName << endl;
  }
  else
  {
    os << indent << "KCoreLabelArrayName : nullptr" << endl;
  }

  os << indent << "Polar               : " << this->Polar << endl;
  os << indent << "Cartesian           : " << this->Cartesian << endl;

  if (this->PolarCoordsRadiusArrayName)
  {
    os << indent << "PolarCoordsRadiusArrayName: " << this->PolarCoordsRadiusArrayName << endl;
  }
  else
  {
    os << indent << "PolarCoordsRadiusArrayName: (none)" << endl;
  }

  if (this->PolarCoordsAngleArrayName)
  {
    os << indent << "PolarCoordsAngleArrayName : " << this->PolarCoordsAngleArrayName << endl;
  }
  else
  {
    os << indent << "PolarCoordsAngleArrayName : (none)" << endl;
  }

  if (this->CartesianCoordsXArrayName)
  {
    os << indent << "CartesianCoordsXArrayName : " << this->CartesianCoordsXArrayName << endl;
  }
  else
  {
    os << indent << "CartesianCoordsXArrayName : (none)" << endl;
  }

  if (this->CartesianCoordsYArrayName)
  {
    os << indent << "CartesianCoordsYArrayName : " << this->CartesianCoordsYArrayName << endl;
  }
  else
  {
    os << indent << "CartesianCoordsYArrayName : (none)" << endl;
  }

  os << indent << "Epsilon    : " << this->Epsilon << endl;
  os << indent << "UnitRadius : " << this->UnitRadius << endl;
}

// -------------------------------------------------------------------------------------------------
void vtkKCoreLayout::SetGraphConnection(vtkAlgorithmOutput* input)
{
  this->SetInputConnection(0, input);
}

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

  return 0;
}

// -------------------------------------------------------------------------------------------------
int vtkKCoreLayout::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  float radius = 0.0f;
  float angle = 0.0f;
  int max_core_level = -1;

  float epsilon = this->Epsilon;
  float unit_radius = this->UnitRadius;

  // std::cout << ">\tvtkKCoreLayout.RequestData()" << endl;

  // get the info objects
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  // get the input and output
  vtkGraph* input = vtkGraph::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkGraph* output = vtkGraph::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  // Send the data to output
  output->ShallowCopy(input);

  // graph size
  vtkIdType num_verts = output->GetNumberOfVertices();

  if (!this->KCoreLabelArrayName)
  {
    const char* kcore = "kcore";
    this->KCoreLabelArrayName = strcpy(new char[strlen(kcore) + 1], kcore);
  }

  // Get the kcore attribute array
  vtkIntArray* kcore_array =
    vtkArrayDownCast<vtkIntArray>(output->GetVertexData()->GetArray(this->KCoreLabelArrayName));

  if (!kcore_array)
  {
    vtkErrorMacro(<< "Vertex attribute array " << this->KCoreLabelArrayName
                  << " is not a vtkIntArray.");
    return 0;
  }

  VTK_CREATE(vtkDoubleArray, arrayX);
  VTK_CREATE(vtkDoubleArray, arrayY);
  VTK_CREATE(vtkDoubleArray, arrayRadius);
  VTK_CREATE(vtkDoubleArray, arrayAngle);

  // Create output arrays (radius, angle) for polar coordinates
  if (this->Cartesian)
  {
    arrayX->SetNumberOfTuples(output->GetNumberOfVertices());
    if (this->CartesianCoordsXArrayName)
    {
      arrayX->SetName(this->CartesianCoordsXArrayName);
    }
    else
    {
      arrayX->SetName("coord_x");
    }

    arrayY->SetNumberOfTuples(output->GetNumberOfVertices());
    if (this->CartesianCoordsXArrayName)
    {
      arrayY->SetName(this->CartesianCoordsXArrayName);
    }
    else
    {
      arrayY->SetName("coord_y");
    }
    output->GetVertexData()->AddArray(arrayX);
    output->GetVertexData()->AddArray(arrayY);
  }

  if (this->Polar)
  {
    arrayRadius->SetNumberOfTuples(output->GetNumberOfVertices());

    if (this->PolarCoordsRadiusArrayName)
    {
      arrayRadius->SetName(this->PolarCoordsRadiusArrayName);
    }
    else
    {
      arrayRadius->SetName("coord_radius");
    }

    arrayAngle->SetNumberOfTuples(output->GetNumberOfVertices());
    if (this->PolarCoordsAngleArrayName)
    {
      arrayAngle->SetName(this->PolarCoordsAngleArrayName);
    }
    else
    {
      arrayAngle->SetName("coord_angle");
    }
    output->GetVertexData()->AddArray(arrayRadius);
    output->GetVertexData()->AddArray(arrayAngle);
  }

  if (!this->Polar && !this->Cartesian)
  {
    vtkWarningMacro(<< "Neither Polar nor Cartesian output coordinate type specified.  Operating "
                       "as a Pass-Through filter");
    return 1;
  }

  // find the maximum core level
  for (vtkIdType i = 0; i < kcore_array->GetNumberOfTuples(); i++)
  {
    max_core_level =
      max_core_level < kcore_array->GetValue(i) ? kcore_array->GetValue(i) : max_core_level;
  }

  // std::cout << "max core level: " << max_core_level << endl;

  // Loop over each vertex and calculate its position
  for (vtkIdType vidx = 0; vidx < num_verts; vidx++)
  {
    int current_level = kcore_array->GetValue(vidx);
    if (current_level == max_core_level)
    {
      radius = unit_radius;
      angle = float(rand() % 100000) / 100000 * 2.0 * vtkMath::Pi();

      // std::cout << vidx << "\t(" << radius << "," << angle << ")" << endl;
      if (this->Cartesian)
      {
        arrayX->SetValue(vidx, radius * cos(angle));
        arrayY->SetValue(vidx, radius * sin(angle));
      }
      if (this->Polar)
      {
        arrayRadius->SetValue(vidx, radius);
        arrayAngle->SetValue(vidx, angle);
      }
    }
    else
    {
      // vertices in shells other than the innermost are positioned in rings around the shells
      // farther in.  By default, each ring is 1 unit out from the next one.  The innermost has
      // a radius of 1.
      int vertex_native_ring = (max_core_level - current_level) + 1;

      // the radius is adjusted by how many of this vertex's neighbors are in the same or
      // higher numbered shells.
      // int neighbors_same_or_higher = 0;
      vtkIdTypeArray* neighbors_same_or_higher = vtkIdTypeArray::New();

      vtkAdjacentVertexIterator* it = vtkAdjacentVertexIterator::New();
      output->GetAdjacentVertices(vidx, it);
      while (it->HasNext())
      {
        vtkIdType nid = it->Next();
        if (kcore_array->GetValue(nid) >= current_level)
        {
          neighbors_same_or_higher->InsertNextValue(nid);
        }
      }
      it->Delete();

      // std::cout << vidx << "\tneighbors_same_or_higher = " <<
      // neighbors_same_or_higher->GetNumberOfTuples() << endl;

      int neighbor_average_ring = 0;
      if (neighbors_same_or_higher->GetNumberOfTuples() > 0)
      {
        for (vtkIdType i = 0; i < neighbors_same_or_higher->GetNumberOfTuples(); i++)
        {
          vtkIdType neighbor_idx = neighbors_same_or_higher->GetValue(i);
          neighbor_average_ring += (1 + max_core_level - kcore_array->GetValue(neighbor_idx));
        }
      }
      else
      {
        // No neighbors - do something semi-reasonable and pretend they're all in the regular
        // ring anyway.
        neighbor_average_ring = vertex_native_ring;
      }
      // std::cout << vidx << "\tneighbor_average_ring = " << neighbor_average_ring << endl;

      // Use epsilon as an interpolation factor between the two ring levels
      radius = (1 - epsilon) * vertex_native_ring + epsilon * neighbor_average_ring;
      radius *= unit_radius;

#ifdef DEBUG_KCORELAYOUT
      std::cout << "DEBUG: Vertex in shell level " << current_level << " (native ring "
                << vertex_native_ring << ", max core level " << max_core_level
                << ") and average neighbor ring " << neighbor_average_ring << " ("
                << neighbors_same_or_higher->GetNumberOfTuples() << " neighbor(s)) is at radius "
                << radius << " with epsilon " << epsilon << endl;
#endif

      // Need the angle.
      angle = float(rand() % 100000) / 100000 * (2.0 * vtkMath::Pi());

      // set the values
      if (this->Cartesian)
      {
        arrayX->SetValue(vidx, radius * cos(angle));
        arrayY->SetValue(vidx, radius * sin(angle));
      }
      if (this->Polar)
      {
        arrayRadius->SetValue(vidx, radius);
        arrayAngle->SetValue(vidx, angle);
      }

      neighbors_same_or_higher->Delete();
    }
  }
  return 1;
}
VTK_ABI_NAMESPACE_END
