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

#include "vtkAbstractArray.h"
#include "vtkCellData.h"
#include "vtkDataArray.h"
#include "vtkDataSet.h"
#include "vtkDataSetAttributes.h"
#include "vtkGraph.h"
#include "vtkIdList.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkIntArray.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkStringArray.h"
#include "vtkTable.h"
#include "vtkVariant.h"
#include <cctype>

#include <map>
#include <utility>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkMapArrayValues);

typedef std::map<vtkVariant, vtkVariant, vtkVariantLessThan> MapBase;
class vtkMapType : public MapBase
{
};

vtkMapArrayValues::vtkMapArrayValues()
{
  this->InputArrayName = nullptr;
  this->OutputArrayName = nullptr;
  this->SetOutputArrayName("ArrayMap");
  this->FieldType = vtkMapArrayValues::POINT_DATA;
  this->OutputArrayType = VTK_INT;
  this->PassArray = 0;
  this->FillValue = -1;

  this->Map = new vtkMapType;
}

vtkMapArrayValues::~vtkMapArrayValues()
{
  this->SetInputArrayName(nullptr);
  this->SetOutputArrayName(nullptr);
  delete this->Map;
}

void vtkMapArrayValues::AddToMap(const char* from, int to)
{
  vtkVariant fromVar(from);
  vtkVariant toVar(to);
  this->Map->insert(std::make_pair(fromVar, toVar));

  this->Modified();
}

void vtkMapArrayValues::AddToMap(int from, int to)
{
  vtkVariant fromVar(from);
  vtkVariant toVar(to);
  this->Map->insert(std::make_pair(fromVar, toVar));

  this->Modified();
}

void vtkMapArrayValues::AddToMap(int from, const char* to)
{
  vtkVariant fromVar(from);
  vtkVariant toVar(to);
  this->Map->insert(std::make_pair(fromVar, toVar));

  this->Modified();
}

void vtkMapArrayValues::AddToMap(const char* from, const char* to)
{
  vtkVariant fromVar(from);
  vtkVariant toVar(to);
  this->Map->insert(std::make_pair(fromVar, toVar));

  this->Modified();
}

void vtkMapArrayValues::AddToMap(vtkVariant from, vtkVariant to)
{
  vtkVariant fromVar(from);
  vtkVariant toVar(to);
  this->Map->insert(std::make_pair(fromVar, toVar));

  this->Modified();
}

void vtkMapArrayValues::ClearMap()
{
  this->Map->clear();

  this->Modified();
}

int vtkMapArrayValues::GetMapSize()
{
  return static_cast<int>(this->Map->size());
}

int vtkMapArrayValues::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  // get the info objects
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

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

  if (!this->InputArrayName)
  {
    // vtkErrorMacro(<<"Input array not specified.");
    output->ShallowCopy(input);
    return 1;
  }

  vtkDataSetAttributes* ods = nullptr;
  if (vtkDataSet::SafeDownCast(input))
  {
    vtkDataSet* dsInput = vtkDataSet::SafeDownCast(input);
    vtkDataSet* dsOutput = vtkDataSet::SafeDownCast(output);
    // This has to be here because it initialized all field datas.
    dsOutput->CopyStructure(dsInput);

    if (dsOutput->GetFieldData() && dsInput->GetFieldData())
    {
      dsOutput->GetFieldData()->PassData(dsInput->GetFieldData());
    }
    dsOutput->GetPointData()->PassData(dsInput->GetPointData());
    dsOutput->GetCellData()->PassData(dsInput->GetCellData());
    switch (this->FieldType)
    {
      case vtkMapArrayValues::POINT_DATA:
        ods = dsOutput->GetPointData();
        break;
      case vtkMapArrayValues::CELL_DATA:
        ods = dsOutput->GetCellData();
        break;
      default:
        vtkErrorMacro(<< "Data must be point or cell for vtkDataSet");
        return 0;
    }
  }
  else if (vtkGraph::SafeDownCast(input))
  {
    vtkGraph* graphInput = vtkGraph::SafeDownCast(input);
    vtkGraph* graphOutput = vtkGraph::SafeDownCast(output);
    graphOutput->ShallowCopy(graphInput);
    switch (this->FieldType)
    {
      case vtkMapArrayValues::VERTEX_DATA:
        ods = graphOutput->GetVertexData();
        break;
      case vtkMapArrayValues::EDGE_DATA:
        ods = graphOutput->GetEdgeData();
        break;
      default:
        vtkErrorMacro(<< "Data must be vertex or edge for vtkGraph");
        return 0;
    }
  }
  else if (vtkTable::SafeDownCast(input))
  {
    vtkTable* tableInput = vtkTable::SafeDownCast(input);
    vtkTable* tableOutput = vtkTable::SafeDownCast(output);
    tableOutput->ShallowCopy(tableInput);
    switch (this->FieldType)
    {
      case vtkMapArrayValues::ROW_DATA:
        ods = tableOutput->GetRowData();
        break;
      default:
        vtkErrorMacro(<< "Data must be row for vtkTable");
        return 0;
    }
  }
  else
  {
    vtkErrorMacro(<< "Invalid input type");
    return 0;
  }

  vtkAbstractArray* inputArray = ods->GetAbstractArray(this->InputArrayName);
  if (!inputArray)
  {
    return 1;
  }
  vtkAbstractArray* outputArray = vtkAbstractArray::CreateArray(this->OutputArrayType);
  vtkDataArray* outputDataArray = vtkArrayDownCast<vtkDataArray>(outputArray);
  vtkStringArray* outputStringArray = vtkArrayDownCast<vtkStringArray>(outputArray);
  outputArray->SetName(this->OutputArrayName);

  // Are we copying the input array values to the output array before
  // the mapping?
  if (this->PassArray)
  {
    // Make sure the DeepCopy will succeed
    if ((inputArray->IsA("vtkDataArray") && outputArray->IsA("vtkDataArray")) ||
      (inputArray->IsA("vtkStringArray") && outputArray->IsA("vtkStringArray")))
    {
      outputArray->DeepCopy(inputArray);
    }
    else
    {
      vtkIdType numComps = inputArray->GetNumberOfComponents();
      vtkIdType numTuples = inputArray->GetNumberOfTuples();
      outputArray->SetNumberOfComponents(numComps);
      outputArray->SetNumberOfTuples(numTuples);
      for (vtkIdType i = 0; i < numTuples; ++i)
      {
        for (vtkIdType j = 0; j < numComps; ++j)
        {
          outputArray->InsertVariantValue(
            i * numComps + j, inputArray->GetVariantValue(i * numComps + j));
        }
      }
    }
  }
  else
  {
    outputArray->SetNumberOfComponents(inputArray->GetNumberOfComponents());
    outputArray->SetNumberOfTuples(inputArray->GetNumberOfTuples());

    // Fill the output array with a default value
    if (outputDataArray)
    {
      outputDataArray->FillComponent(0, this->FillValue);
    }
  }

  // Use the internal map to set the mapped values in the output array
  vtkIdList* results = vtkIdList::New();
  for (MapBase::iterator i = this->Map->begin(); i != this->Map->end(); ++i)
  {
    inputArray->LookupValue(i->first, results);
    for (vtkIdType j = 0; j < results->GetNumberOfIds(); ++j)
    {
      if (outputDataArray)
      {
        outputDataArray->SetComponent(results->GetId(j), 0, i->second.ToDouble());
      }
      else if (outputStringArray)
      {
        outputStringArray->SetValue(results->GetId(j), i->second.ToString());
      }
    }
  }

  // Finally, add the array to the appropriate vtkDataSetAttributes
  ods->AddArray(outputArray);

  results->Delete();
  outputArray->Delete();

  return 1;
}

int vtkMapArrayValues::FillInputPortInformation(int vtkNotUsed(port), vtkInformation* info)
{
  // This algorithm may accept a vtkPointSet or vtkGraph.
  info->Remove(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE());
  info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet");
  info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkGraph");
  info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkTable");
  return 1;
}

void vtkMapArrayValues::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Input array name: ";
  if (this->InputArrayName)
  {
    os << this->InputArrayName << endl;
  }
  else
  {
    os << "(none)" << endl;
  }

  os << indent << "Output array name: ";
  if (this->OutputArrayName)
  {
    os << this->OutputArrayName << endl;
  }
  else
  {
    os << "(none)" << endl;
  }
  os << indent << "Field type: " << this->FieldType << endl;
  os << indent << "Output array type: " << this->OutputArrayType << endl;
  os << indent << "PassArray: " << this->PassArray << endl;
  os << indent << "FillValue: " << this->FillValue << endl;
}
VTK_ABI_NAMESPACE_END
