/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkAssignAttribute.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkAssignAttribute.h"

#include "vtkCellData.h"
#include "vtkDataArray.h"
#include "vtkDataSet.h"
#include "vtkDataSetAttributes.h"
#include "vtkGraph.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include <cctype>

#include <cassert>

static int vtkGetArrayIndex(vtkDataSetAttributes* dsa, vtkAbstractArray* array)
{
  for (int cc = 0; cc < dsa->GetNumberOfArrays(); cc++)
  {
    if (dsa->GetAbstractArray(cc) == array)
    {
      return cc;
    }
  }
  return -1;
}

vtkStandardNewMacro(vtkAssignAttribute);

char vtkAssignAttribute::AttributeLocationNames[vtkAssignAttribute::NUM_ATTRIBUTE_LOCS][12] = {
  "POINT_DATA", "CELL_DATA", "VERTEX_DATA", "EDGE_DATA"
};

char vtkAssignAttribute::AttributeNames[vtkDataSetAttributes::NUM_ATTRIBUTES][20] = { { 0 } };

vtkAssignAttribute::vtkAssignAttribute()
{
  this->FieldName = nullptr;
  this->AttributeLocationAssignment = -1;
  this->AttributeType = -1;
  this->InputAttributeType = -1;
  this->FieldTypeAssignment = -1;

  // convert the attribute names to uppercase for local use
  if (vtkAssignAttribute::AttributeNames[0][0] == 0)
  {
    for (int i = 0; i < vtkDataSetAttributes::NUM_ATTRIBUTES; i++)
    {
      int l = static_cast<int>(strlen(vtkDataSetAttributes::GetAttributeTypeAsString(i)));
      for (int c = 0; c < l && c < 19; c++)
      {
        vtkAssignAttribute::AttributeNames[i][c] =
          toupper(vtkDataSetAttributes::GetAttributeTypeAsString(i)[c]);
      }
    }
  }
}

vtkAssignAttribute::~vtkAssignAttribute()
{
  delete[] this->FieldName;
  this->FieldName = nullptr;
}

void vtkAssignAttribute::Assign(const char* fieldName, int attributeType, int attributeLoc)
{
  if (!fieldName)
  {
    return;
  }

  if ((attributeType < 0) || (attributeType > vtkDataSetAttributes::NUM_ATTRIBUTES))
  {
    vtkErrorMacro("Wrong attribute type.");
    return;
  }

  if ((attributeLoc < 0) || (attributeLoc > vtkAssignAttribute::NUM_ATTRIBUTE_LOCS))
  {
    vtkErrorMacro("The source for the field is wrong.");
    return;
  }

  this->Modified();
  delete[] this->FieldName;
  this->FieldName = new char[strlen(fieldName) + 1];
  strcpy(this->FieldName, fieldName);

  this->AttributeType = attributeType;
  this->AttributeLocationAssignment = attributeLoc;
  this->FieldTypeAssignment = vtkAssignAttribute::NAME;
}

void vtkAssignAttribute::Assign(int inputAttributeType, int attributeType, int attributeLoc)
{
  if ((attributeType < 0) || (attributeType > vtkDataSetAttributes::NUM_ATTRIBUTES) ||
    (inputAttributeType < 0) || (inputAttributeType > vtkDataSetAttributes::NUM_ATTRIBUTES))
  {
    vtkErrorMacro("Wrong attribute type.");
    return;
  }

  if ((attributeLoc < 0) || (attributeLoc > vtkAssignAttribute::NUM_ATTRIBUTE_LOCS))
  {
    vtkErrorMacro("The source for the field is wrong.");
    return;
  }

  this->Modified();
  this->AttributeType = attributeType;
  this->InputAttributeType = inputAttributeType;
  this->AttributeLocationAssignment = attributeLoc;
  this->FieldTypeAssignment = vtkAssignAttribute::ATTRIBUTE;
}

void vtkAssignAttribute::Assign(
  const char* name, const char* attributeType, const char* attributeLoc)
{
  if (!name || !attributeType || !attributeLoc)
  {
    return;
  }

  int numAttr = vtkDataSetAttributes::NUM_ATTRIBUTES;
  int numAttributeLocs = vtkAssignAttribute::NUM_ATTRIBUTE_LOCS;
  int i;

  // Convert strings to ints and call the appropriate Assign()
  int inputAttributeType = -1;
  for (i = 0; i < numAttr; i++)
  {
    if (!strcmp(name, AttributeNames[i]))
    {
      inputAttributeType = i;
      break;
    }
  }

  int attrType = -1;
  for (i = 0; i < numAttr; i++)
  {
    if (!strcmp(attributeType, AttributeNames[i]))
    {
      attrType = i;
      break;
    }
  }
  if (attrType == -1)
  {
    vtkErrorMacro("Target attribute type is invalid.");
    return;
  }

  int loc = -1;
  for (i = 0; i < numAttributeLocs; i++)
  {
    if (!strcmp(attributeLoc, AttributeLocationNames[i]))
    {
      loc = i;
      break;
    }
  }
  if (loc == -1)
  {
    vtkErrorMacro("Target location for the attribute is invalid.");
    return;
  }

  if (inputAttributeType == -1)
  {
    this->Assign(name, attrType, loc);
  }
  else
  {
    this->Assign(inputAttributeType, attrType, loc);
  }
}

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

  if ((this->AttributeType != -1) && (this->AttributeLocationAssignment != -1) &&
    (this->FieldTypeAssignment != -1))
  {
    int fieldAssociation = vtkDataObject::FIELD_ASSOCIATION_POINTS;
    switch (this->AttributeLocationAssignment)
    {
      case POINT_DATA:
        fieldAssociation = vtkDataObject::FIELD_ASSOCIATION_POINTS;
        break;
      case CELL_DATA:
        fieldAssociation = vtkDataObject::FIELD_ASSOCIATION_CELLS;
        break;
      case VERTEX_DATA:
        fieldAssociation = vtkDataObject::FIELD_ASSOCIATION_VERTICES;
        break;
      default:
        fieldAssociation = vtkDataObject::FIELD_ASSOCIATION_EDGES;
        break;
    }
    if (this->FieldTypeAssignment == vtkAssignAttribute::NAME && this->FieldName)
    {
      vtkDataObject::SetActiveAttribute(
        outInfo, fieldAssociation, this->FieldName, this->AttributeType);
      vtkInformation* inputAttributeInfo =
        vtkDataObject::GetNamedFieldInformation(inInfo, fieldAssociation, this->FieldName);
      if (inputAttributeInfo)
      {
        const int type = inputAttributeInfo->Get(vtkDataObject::FIELD_ARRAY_TYPE());
        const int numComponents =
          inputAttributeInfo->Get(vtkDataObject::FIELD_NUMBER_OF_COMPONENTS());
        const int numTuples = inputAttributeInfo->Get(vtkDataObject::FIELD_NUMBER_OF_TUPLES());

        vtkDataObject::SetActiveAttributeInfo(outInfo, fieldAssociation, this->AttributeType,
          this->FieldName, type, numComponents, numTuples);
      }
    }
    else if (this->FieldTypeAssignment == vtkAssignAttribute::ATTRIBUTE &&
      this->InputAttributeType != -1)
    {
      vtkInformation* inputAttributeInfo = vtkDataObject::GetActiveFieldInformation(
        inInfo, fieldAssociation, this->InputAttributeType);
      if (inputAttributeInfo) // do we have an active field of requested type
      {
        const char* name = inputAttributeInfo->Get(vtkDataObject::FIELD_NAME());
        const int type = inputAttributeInfo->Get(vtkDataObject::FIELD_ARRAY_TYPE());
        const int numComponents =
          inputAttributeInfo->Get(vtkDataObject::FIELD_NUMBER_OF_COMPONENTS());
        const int numTuples = inputAttributeInfo->Get(vtkDataObject::FIELD_NUMBER_OF_TUPLES());

        vtkDataObject::SetActiveAttribute(outInfo, fieldAssociation, name, this->AttributeType);
        vtkDataObject::SetActiveAttributeInfo(
          outInfo, fieldAssociation, this->AttributeType, name, type, numComponents, numTuples);
      }
    }
  }

  return 1;
}

int vtkAssignAttribute::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());

  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->AttributeLocationAssignment)
    {
      case vtkAssignAttribute::POINT_DATA:
        ods = dsOutput->GetPointData();
        break;
      case vtkAssignAttribute::CELL_DATA:
        ods = dsOutput->GetCellData();
        break;
      default:
        vtkErrorMacro(<< "Data must be point or cell for vtkDataSet");
        return 0;
    }
  }
  else
  {
    vtkGraph* graphInput = vtkGraph::SafeDownCast(input);
    vtkGraph* graphOutput = vtkGraph::SafeDownCast(output);
    graphOutput->ShallowCopy(graphInput);
    switch (this->AttributeLocationAssignment)
    {
      case vtkAssignAttribute::VERTEX_DATA:
        ods = graphOutput->GetVertexData();
        break;
      case vtkAssignAttribute::EDGE_DATA:
        ods = graphOutput->GetEdgeData();
        break;
      default:
        vtkErrorMacro(<< "Data must be vertex or edge for vtkGraph");
        return 0;
    }
  }

  if ((this->AttributeType != -1) && (this->AttributeLocationAssignment != -1) &&
    (this->FieldTypeAssignment != -1))
  {
    // Get the appropriate output DataSetAttributes
    if (this->FieldTypeAssignment == vtkAssignAttribute::NAME && this->FieldName)
    {
      ods->SetActiveAttribute(this->FieldName, this->AttributeType);
    }
    else if (this->FieldTypeAssignment == vtkAssignAttribute::ATTRIBUTE &&
      (this->InputAttributeType != -1))
    {
      // If labeling an attribute as another attribute, we
      // need to get it's index and call SetActiveAttribute()
      // with that index
      // int attributeIndices[vtkDataSetAttributes::NUM_ATTRIBUTES];
      // ods->GetAttributeIndices(attributeIndices);
      // if (attributeIndices[this->InputAttributeType] != -1)
      vtkAbstractArray* oaa = ods->GetAbstractAttribute(this->InputAttributeType);
      if (oaa)
      {
        // Use array index to mark this array active and not its name since VTK
        // arrays not necessarily have names.
        int arrIndex = vtkGetArrayIndex(ods, oaa);
        assert(arrIndex >= 0); // arrIndex cannot be -1. Doesn't make sense.
        ods->SetActiveAttribute(arrIndex, this->AttributeType);
      }
    }
  }

  return 1;
}

int vtkAssignAttribute::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");
  return 1;
}

void vtkAssignAttribute::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Field name: ";
  if (this->FieldName)
  {
    os << this->FieldName << endl;
  }
  else
  {
    os << "(none)" << endl;
  }
  os << indent << "Field type: " << this->FieldTypeAssignment << endl;
  os << indent << "Attribute type: " << this->AttributeType << endl;
  os << indent << "Input attribute type: " << this->InputAttributeType << endl;
  os << indent << "Attribute location: " << this->AttributeLocationAssignment << endl;
}
