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

#include "vtkAbstractArray.h"
#include "vtkCellAttribute.h"
#include "vtkCellGrid.h"
#include "vtkDataSetAttributes.h"
#include "vtkIdList.h"
#include "vtkObjectFactory.h"

#include <algorithm>

VTK_ABI_NAMESPACE_BEGIN

vtkStandardNewMacro(vtkCellGridCopyQuery);
vtkCxxSetObjectMacro(vtkCellGridCopyQuery, Source, vtkCellGrid);
vtkCxxSetObjectMacro(vtkCellGridCopyQuery, Target, vtkCellGrid);

vtkCellGridCopyQuery::~vtkCellGridCopyQuery()
{
  this->SetSource(nullptr);
  this->SetTarget(nullptr);
  this->ResetCellAttributeIds();
}

void vtkCellGridCopyQuery::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Source: " << this->Source << "\n";
  os << indent << "Target: " << this->Target << "\n";
  os << indent << "CopyCells: " << (this->CopyCells ? "Y" : "N") << "\n";
  os << indent << "CopyOnlyShape: " << (this->CopyOnlyShape ? "Y" : "N") << "\n";
  os << indent << "CopyArrays: " << (this->CopyArrays ? "Y" : "N") << "\n";
  os << indent << "CopyArrayValues: " << (this->CopyArrayValues ? "Y" : "N") << "\n";
  os << indent << "DeepCopyArrays: " << (this->DeepCopyArrays ? "Y" : "N") << "\n";
  os << indent << "CopySchema: " << (this->CopySchema ? "Y" : "N") << "\n";
  os << indent << "CellAttributeIds: ";
  for (const auto& id : this->CellAttributeIds)
  {
    os << " " << id;
  }
  if (this->CellAttributeIds.empty())
  {
    os << " (empty)";
  }
  os << "\n";
  vtkIndent i2 = indent.GetNextIndent();
  os << indent << "ArrayMap: " << this->ArrayMap.size() << " entries\n";
  for (const auto& entry : this->ArrayMap)
  {
    if (!entry.first || !entry.second)
    {
      os << i2 << entry.first << ": " << entry.second << "\n";
    }
    else
    {
      os << i2 << entry.first << " (" << entry.first->GetName() << "): " << entry.second << " ("
         << entry.second->GetName() << ")\n";
    }
  }
  os << indent << "AttributeMap: " << this->AttributeMap.size() << " entries\n";
  for (const auto& entry : this->AttributeMap)
  {
    if (!entry.first || !entry.second)
    {
      os << i2 << entry.first << ": " << entry.second << "\n";
    }
    else
    {
      os << i2 << entry.first << " (" << entry.first->GetName().Data() << "): " << entry.second
         << " (" << entry.second->GetName().Data() << ")\n";
    }
  }
}

bool vtkCellGridCopyQuery::Initialize()
{
  bool ok = this->Superclass::Initialize();
  this->ArrayMap.clear();
  this->AttributeMap.clear();
  return ok;
}

bool vtkCellGridCopyQuery::Finalize()
{
  // Copy cell-attribute range caches, but only when performing shallow
  // copies as deep copies indicate that values will likely be changed
  // downstream.
  if (this->CopyArrays && this->CopyArrayValues && !this->DeepCopyArrays)
  {
    const auto& sourceRangeCache(this->Source->GetRangeCache());
    auto& targetRangeCache(this->Target->GetRangeCache());
    for (const auto& entry : this->AttributeMap)
    {
      auto it = sourceRangeCache.find(entry.first);
      if (it != sourceRangeCache.end())
      {
        targetRangeCache[entry.second] = it->second;
      }
    }
  }

  this->ArrayMap.clear();
  this->AttributeMap.clear();

  if (this->CopySchema)
  {
    this->Target->SetSchema(this->Source->GetSchemaName(), this->Source->GetSchemaVersion());
  }

  if (this->Source->GetContentVersion() > 0)
  {
    this->Target->SetContentVersion(this->Source->GetContentVersion() + 1);
  }

  // Do not copy the integer attribute counter:
  // this->Target->NextAttribute = this->Source->NextAttribute;
  return true;
}

bool vtkCellGridCopyQuery::AddSourceCellAttributeId(int attributeId)
{
  return this->CellAttributeIds.insert(attributeId).second;
}

bool vtkCellGridCopyQuery::RemoveSourceCellAttributeId(int attributeId)
{
  return this->CellAttributeIds.erase(attributeId) > 0;
}

bool vtkCellGridCopyQuery::AddAllSourceCellAttributeIds()
{
  if (this->Source)
  {
    std::size_t nn = this->CellAttributeIds.size();
    auto allIds = this->Source->GetCellAttributeIds();
    this->CellAttributeIds.insert(allIds.begin(), allIds.end());
    return nn != this->CellAttributeIds.size();
  }
  return false;
}

void vtkCellGridCopyQuery::GetCellAttributeIds(vtkIdList* ids) const
{
  if (!ids)
  {
    vtkErrorMacro("Null ids passed to " << __func__ << ".");
    return;
  }
  ids->Initialize();
  ids->SetNumberOfIds(this->CellAttributeIds.size());
  int ii = 0;
  for (const auto& id : this->CellAttributeIds)
  {
    ids->SetId(ii, id);
    ++ii;
  }
}

void vtkCellGridCopyQuery::ResetCellAttributeIds()
{
  this->CellAttributeIds.clear();
}

void vtkCellGridCopyQuery::CopyAttributeArrays(vtkCellAttribute* srcAtt, vtkStringToken cellType)
{
  if (!this->CopyArrays)
  {
    return;
  }
  if (!srcAtt)
  {
    vtkErrorMacro("Null source attribute.");
    return;
  }
  auto srcCellTypeInfo = srcAtt->GetCellTypeInfo(cellType);
  auto& arraysByRole = srcCellTypeInfo.ArraysByRole;
  for (const auto& roleEntry : arraysByRole)
  {
    auto srcArr = roleEntry.second;
    int arrType = this->Source->GetAttributeTypeForArray(srcArr);
    auto* tgtGroup = this->Target->GetAttributes(arrType);
    if (this->CopyArrayValues && !this->DeepCopyArrays)
    {
      // We should copy by referencing the original array;
      // just add the source array to the destination.
      tgtGroup->AddArray(srcArr);
    }
    else
    {
      // Deep-copy the source array as needed.
      // I. See if we've already copied the array:
      auto mapIt = this->ArrayMap.find(srcArr);
      if (mapIt != this->ArrayMap.end())
      {
        // No need to re-create the array; we've already copied it.
        // Just add it to the array grouping.
        tgtGroup->AddArray(mapIt->second);
        continue;
      }
      // II. We need to create an array.
      auto* tgtArr = vtkAbstractArray::CreateArray(srcArr->GetDataType());
      if (this->CopyArrayValues)
      {
        // Create a deep copy including the values:
        tgtArr->DeepCopy(srcArr);
      }
      else
      {
        // Copy array "metadata" only.
        if (srcArr->HasInformation())
        {
          tgtArr->CopyInformation(srcArr->GetInformation(), /*deep*/ 1);
        }
        tgtArr->SetName(srcArr->GetName());
        tgtArr->SetNumberOfComponents(srcArr->GetNumberOfComponents());
        tgtArr->CopyComponentNames(srcArr);
      }
      tgtGroup->AddArray(tgtArr);
      this->ArrayMap[srcArr] = tgtArr;
      tgtArr->Delete();
    }
  }
}

vtkCellAttribute* vtkCellGridCopyQuery::CopyOrUpdateAttributeRecord(
  vtkCellAttribute* srcAtt, vtkStringToken cellType)
{
  // If we already have the attribute, use it.  Otherwise, create it.
  vtkCellAttribute* targetAttribute = nullptr;
  auto amit = this->AttributeMap.find(srcAtt);
  if (amit == this->AttributeMap.end())
  {
    // We need to create the attribute.
    vtkNew<vtkCellAttribute> tgtAtt;
    tgtAtt->ShallowCopy(srcAtt, /* do not copy arrays for cell types*/ false);
    targetAttribute = tgtAtt;

    // Copy cached range data if we are copying cells.
    if (this->GetCopyCells() && this->GetCopyArrayValues())
    {
      const auto& srcRange = this->Source->GetRangeCache();
      auto& tgtRange = this->Target->GetRangeCache();

      auto cacheIt = srcRange.find(srcAtt);
      if (cacheIt != srcRange.end())
      {
        tgtRange[tgtAtt].resize(tgtAtt->GetNumberOfComponents() + 2);
        std::size_t ii = 0;
        for (const auto& componentRange : cacheIt->second)
        {
          if (componentRange.FiniteRangeTime > srcAtt->GetMTime())
          {
            tgtRange[tgtAtt][ii].FiniteRange = cacheIt->second[ii].FiniteRange;
            tgtRange[tgtAtt][ii].FiniteRangeTime.Modified();
          }
          if (componentRange.EntireRangeTime > srcAtt->GetMTime())
          {
            tgtRange[tgtAtt][ii].EntireRange = cacheIt->second[ii].EntireRange;
            tgtRange[tgtAtt][ii].EntireRangeTime.Modified();
          }
          ++ii;
        }
      }
    }

    this->Target->AddCellAttribute(tgtAtt);
    this->AttributeMap[srcAtt] = tgtAtt;
    if (this->Source->GetShapeAttribute() == srcAtt)
    {
      this->Target->SetShapeAttribute(tgtAtt);
    }
  }
  else
  {
    targetAttribute = amit->second;
  }

  if (!this->CopyCellTypes)
  {
    return targetAttribute;
  }

  // Regardless of whether the attribute pre-existed or not,
  // add arrays for each cell type.
  auto oldCellTypeInfo = srcAtt->GetCellTypeInfo(cellType);
  auto& oldArraysForCellType = oldCellTypeInfo.ArraysByRole;
  vtkCellAttribute::CellTypeInfo newCellTypeInfo;
  auto& arraysForCellType = newCellTypeInfo.ArraysByRole;

  newCellTypeInfo.DOFSharing = oldCellTypeInfo.DOFSharing;
  newCellTypeInfo.FunctionSpace = oldCellTypeInfo.FunctionSpace;
  newCellTypeInfo.Basis = oldCellTypeInfo.Basis;
  newCellTypeInfo.Order = oldCellTypeInfo.Order;
  for (const auto& entry : oldArraysForCellType)
  {
    auto it = this->ArrayMap.find(entry.second);
    if (it != this->ArrayMap.end())
    {
      arraysForCellType[entry.first] = it->second;
    }
    else if (this->CopyArrays && this->CopyArrayValues && !this->DeepCopyArrays)
    {
      arraysForCellType[entry.first] = entry.second;
    }
  }
  targetAttribute->SetCellTypeInfo(cellType, newCellTypeInfo);

  return targetAttribute;
}

VTK_ABI_NAMESPACE_END
