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

#include <algorithm>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkEdgeTable);

//------------------------------------------------------------------------------
// Instantiate object based on maximum point id.
vtkEdgeTable::vtkEdgeTable()
{
  this->Points = nullptr;
  this->TableMaxId = -1;
  this->Position[0] = 0;
  this->Position[1] = -1;
  this->NumberOfEdges = 0;
}

//------------------------------------------------------------------------------
// Free memory and return to instantiated state.
void vtkEdgeTable::Initialize()
{
  if (!this->Table.empty())
  {
    this->Table.clear();
    this->TableMaxId = -1;

    if (this->StoreAttributes == 1)
    {
      this->Attributes.clear();
    }
    else if (this->StoreAttributes == 2)
    {
      this->PointerAttributes.clear();
    }
  } // if table defined

  if (this->Points)
  {
    this->Points->Delete();
    this->Points = nullptr;
  }

  this->NumberOfEdges = 0;
}

//------------------------------------------------------------------------------
// Free memory and return to instantiated state.
void vtkEdgeTable::Reset()
{
  if (!this->Table.empty())
  {
    for (vtkSmartPointer<vtkIdList>& table : this->Table)
    {
      if (table)
      {
        table->Reset();
      }
    }

    if (this->StoreAttributes == 1 && !this->Attributes.empty())
    {
      for (vtkSmartPointer<vtkIdList>& table : this->Attributes)
      {
        if (table)
        {
          table->Reset();
        }
      }
    }
    else if (this->StoreAttributes == 2 && !this->PointerAttributes.empty())
    {
      for (std::vector<void*>& table : this->PointerAttributes)
      {
        if (!table.empty())
        {
          table.clear();
        }
      }
    }
  } // if table defined

  this->TableMaxId = -1;

  if (this->Points)
  {
    this->Points->Reset();
  }

  this->NumberOfEdges = 0;
}

//------------------------------------------------------------------------------
vtkEdgeTable::~vtkEdgeTable()
{
  this->Initialize();
}

//------------------------------------------------------------------------------
int vtkEdgeTable::InitEdgeInsertion(vtkIdType numPoints, int storeAttributes)
{
  numPoints = std::max<vtkIdType>(1, numPoints);

  // Discard old memory if not enough has been previously allocated
  this->StoreAttributes = storeAttributes;
  this->TableMaxId = -1;

  if (numPoints > static_cast<vtkIdType>(this->Table.size()))
  {
    this->Initialize();
    this->Table.resize(numPoints, nullptr);

    if (this->StoreAttributes == 1)
    {
      this->Attributes.resize(numPoints, nullptr);
    }
    else if (this->StoreAttributes == 2)
    {
      this->PointerAttributes.resize(numPoints, std::vector<void*>());
    }
  }

  // Otherwise, reuse the old memory
  else
  {
    this->Reset();
  }

  this->Position[0] = 0;
  this->Position[1] = -1;

  this->NumberOfEdges = 0;

  return 1;
}

//------------------------------------------------------------------------------
// Return non-negative if edge (p1,p2) is an edge; otherwise -1.
vtkIdType vtkEdgeTable::IsEdge(vtkIdType p1, vtkIdType p2)
{
  vtkIdType index, search;

  if (p1 < p2)
  {
    index = p1;
    search = p2;
  }
  else
  {
    index = p2;
    search = p1;
  }

  if (index > this->TableMaxId || this->Table[index] == nullptr)
  {
    return (-1);
  }
  else
  {
    vtkIdType loc;
    if ((loc = this->Table[index]->IsId(search)) == (-1))
    {
      return (-1);
    }
    else
    {
      if (this->StoreAttributes == 1)
      {
        return this->Attributes[index]->GetId(loc);
      }
      else
      {
        return 1;
      }
    }
  }
}

//------------------------------------------------------------------------------
// Return non-negative if edge (p1,p2) is an edge; otherwise -1.
void vtkEdgeTable::IsEdge(vtkIdType p1, vtkIdType p2, void*& ptr)
{
  vtkIdType index, search;

  if (p1 < p2)
  {
    index = p1;
    search = p2;
  }
  else
  {
    index = p2;
    search = p1;
  }

  if (index > this->TableMaxId || this->Table[index] == nullptr)
  {
    ptr = nullptr;
  }
  else
  {
    vtkIdType loc;
    if ((loc = this->Table[index]->IsId(search)) == (-1))
    {
      ptr = nullptr;
    }
    else
    {
      if (this->StoreAttributes == 2)
      {
        ptr = this->PointerAttributes[index][loc];
      }
      else
      {
        ptr = nullptr;
      }
    }
  }
}

//------------------------------------------------------------------------------
// Insert the edge (p1,p2) into the table. It is the user's responsibility to
// check if the edge has already been inserted.
vtkIdType vtkEdgeTable::InsertEdge(vtkIdType p1, vtkIdType p2)
{
  vtkIdType index, search;

  if (p1 < p2)
  {
    index = p1;
    search = p2;
  }
  else
  {
    index = p2;
    search = p1;
  }

  if (index >= static_cast<vtkIdType>(this->Table.size()))
  {
    this->Resize(index + 1);
  }

  this->TableMaxId = std::max(index, this->TableMaxId);

  if (this->Table[index] == nullptr)
  {
    this->Table[index] = vtkSmartPointer<vtkIdList>::New();
    this->Table[index]->Reserve(6);
    if (this->StoreAttributes == 1)
    {
      this->Attributes[index] = vtkSmartPointer<vtkIdList>::New();
      this->Attributes[index]->Reserve(6);
    }
  }

  this->Table[index]->InsertNextId(search);
  if (this->StoreAttributes == 1)
  {
    this->Attributes[index]->InsertNextId(this->NumberOfEdges);
  }
  this->NumberOfEdges++;

  return (this->NumberOfEdges - 1);
}

//------------------------------------------------------------------------------
void vtkEdgeTable::InsertEdge(vtkIdType p1, vtkIdType p2, vtkIdType attributeId)
{
  vtkIdType index, search;

  if (p1 < p2)
  {
    index = p1;
    search = p2;
  }
  else
  {
    index = p2;
    search = p1;
  }

  if (index >= static_cast<vtkIdType>(this->Table.size()))
  {
    this->Resize(index + 1);
  }

  this->TableMaxId = std::max(index, this->TableMaxId);

  if (this->Table[index] == nullptr)
  {
    this->Table[index] = vtkSmartPointer<vtkIdList>::New();
    this->Table[index]->Reserve(6);
    if (this->StoreAttributes == 1)
    {
      this->Attributes[index] = vtkSmartPointer<vtkIdList>::New();
      this->Attributes[index]->Reserve(6);
    }
  }

  this->NumberOfEdges++;
  this->Table[index]->InsertNextId(search);
  if (this->StoreAttributes)
  {
    this->Attributes[index]->InsertNextId(attributeId);
  }
}

//------------------------------------------------------------------------------
void vtkEdgeTable::InsertEdge(vtkIdType p1, vtkIdType p2, void* ptr)
{
  vtkIdType index, search;

  if (p1 < p2)
  {
    index = p1;
    search = p2;
  }
  else
  {
    index = p2;
    search = p1;
  }

  if (index >= static_cast<vtkIdType>(this->Table.size()))
  {
    this->Resize(index + 1);
  }

  this->TableMaxId = std::max(index, this->TableMaxId);

  if (this->Table[index] == nullptr)
  {
    this->Table[index] = vtkSmartPointer<vtkIdList>::New();
    this->Table[index]->Reserve(6);
    if (this->StoreAttributes == 2)
    {
      this->PointerAttributes[index].reserve(6);
    }
  }

  this->NumberOfEdges++;
  this->Table[index]->InsertNextId(search);
  if (this->StoreAttributes == 2)
  {
    this->PointerAttributes[index].push_back(ptr);
  }
}

//------------------------------------------------------------------------------
// Initialize traversal of edges in table.
void vtkEdgeTable::InitTraversal()
{
  this->Position[0] = 0;
  this->Position[1] = -1;
}

// Traverse list of edges in table. Return the edge as (p1,p2), where p1 and
// p2 are point id's. Method return value is <0 if the list is exhausted;
// otherwise a valid id >=0. The value of p1 is guaranteed to be <= p2. The
// return value is an id that can be used for accessing attributes.
vtkIdType vtkEdgeTable::GetNextEdge(vtkIdType& p1, vtkIdType& p2)
{
  for (; this->Position[0] <= this->TableMaxId; this->Position[0]++, this->Position[1] = (-1))
  {
    if (this->Table[this->Position[0]] != nullptr &&
      ++this->Position[1] < this->Table[this->Position[0]]->GetNumberOfIds())
    {
      p1 = this->Position[0];
      p2 = this->Table[this->Position[0]]->GetId(this->Position[1]);
      if (this->StoreAttributes == 1)
      {
        return this->Attributes[this->Position[0]]->GetId(this->Position[1]);
      }
      else
      {
        return (-1);
      }
    }
  }

  return (-1);
}

//------------------------------------------------------------------------------
// Traverse list of edges in table. Return the edge as (p1,p2), where p1 and
// p2 are point id's. The value of p1 is guaranteed to be <= p2. The
// return value is either 1 for success or 0 if the list is exhausted.
int vtkEdgeTable::GetNextEdge(vtkIdType& p1, vtkIdType& p2, void*& ptr)
{
  for (; this->Position[0] <= this->TableMaxId; this->Position[0]++, this->Position[1] = (-1))
  {
    if (this->Table[this->Position[0]] != nullptr &&
      ++this->Position[1] < this->Table[this->Position[0]]->GetNumberOfIds())
    {
      p1 = this->Position[0];
      p2 = this->Table[this->Position[0]]->GetId(this->Position[1]);
      if (this->StoreAttributes == 2)
      {
        this->IsEdge(p1, p2, ptr);
      }
      else
      {
        ptr = nullptr;
      }
      return 1;
    }
  }
  return 0;
}

void vtkEdgeTable::Resize(vtkIdType size)
{
  vtkIdType extend = this->Table.size() / 2 + 1, newSize;

  if (size >= static_cast<vtkIdType>(this->Table.size()))
  {
    newSize = this->Table.size() + extend * (((size - this->Table.size()) / extend) + 1);
  }
  else
  {
    newSize = size;
  }

  this->Table.resize(newSize);

  if (this->StoreAttributes == 1)
  {
    this->Attributes.resize(newSize);
  }
  else if (this->StoreAttributes == 2)
  {
    this->PointerAttributes.resize(newSize);
  }
}

//------------------------------------------------------------------------------
int vtkEdgeTable::InitPointInsertion(vtkPoints* newPts, vtkIdType estSize)
{
  // Initialize
  if (!this->Table.empty())
  {
    this->Initialize();
  }
  if (newPts == nullptr)
  {
    vtkErrorMacro(<< "Must define points for point insertion");
    return 0;
  }
  if (this->Points != nullptr)
  {
    this->Points->Delete();
  }
  // Set up the edge insertion
  this->InitEdgeInsertion(estSize, 1);

  this->Points = newPts;
  this->Points->Register(this);

  return 1;
}

//------------------------------------------------------------------------------
int vtkEdgeTable::InsertUniquePoint(vtkIdType p1, vtkIdType p2, double x[3], vtkIdType& ptId)
{
  vtkIdType loc = this->IsEdge(p1, p2);

  if (loc != -1)
  {
    ptId = loc;
    return 0;
  }
  else
  {
    ptId = this->InsertEdge(p1, p2);
    this->Points->InsertPoint(ptId, x);
    return 1;
  }
}

//------------------------------------------------------------------------------
void vtkEdgeTable::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "NumberOfEdges: " << this->GetNumberOfEdges() << "\n";
}
VTK_ABI_NAMESPACE_END
