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

  Program:   Visualization Toolkit
  Module:    vtkSplitColumnComponents.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 "vtkSplitColumnComponents.h"

#include "vtkAbstractArray.h"
#include "vtkFieldData.h"
#include "vtkInformation.h"
#include "vtkInformationIntegerKey.h"
#include "vtkInformationStringKey.h"
#include "vtkInformationVector.h"
#include "vtkIntArray.h"
#include "vtkObjectFactory.h"
#include "vtkStringArray.h"
#include "vtkTable.h"

#include <cmath>
#include <sstream>

vtkStandardNewMacro(vtkSplitColumnComponents);
vtkInformationKeyMacro(vtkSplitColumnComponents, ORIGINAL_ARRAY_NAME, String);
vtkInformationKeyMacro(vtkSplitColumnComponents, ORIGINAL_COMPONENT_NUMBER, Integer);
//---------------------------------------------------------------------------
vtkSplitColumnComponents::vtkSplitColumnComponents()
  : CalculateMagnitudes(true)
  , NamingMode(vtkSplitColumnComponents::NUMBERS_WITH_PARENS)
{
  this->SetNumberOfInputPorts(1);
  this->SetNumberOfOutputPorts(1);
}

//---------------------------------------------------------------------------
vtkSplitColumnComponents::~vtkSplitColumnComponents() = default;

//---------------------------------------------------------------------------
// Templated function in an anonymous namespace to copy the data from the
// specified component in one column to a single component column
namespace {

template<typename T>
void CopyArrayData(T* source, T* destination, int components, int c,
                   unsigned int length)
{
  for (unsigned int i = 0; i < length; ++i)
  {
    destination[i] = source[i*components + c];
  }
}

template<typename T>
void CalculateMagnitude(T* source, T* destination, int components,
                        unsigned int length)
{
  for (unsigned int i = 0; i < length; ++i)
  {
    double tmp = 0.0;
    for (int j = 0; j < components; ++j)
    {
      tmp += static_cast<double>(source[i*components + j]
                                 * source[i*components + j]);
    }
    destination[i] = static_cast<T>(sqrt(tmp));
  }
}

} // End of anonymous namespace

//---------------------------------------------------------------------------
int vtkSplitColumnComponents::RequestData(
  vtkInformation*,
  vtkInformationVector** inputVector,
  vtkInformationVector* outputVector)
{
  // Get input tables
  vtkInformation* table1Info = inputVector[0]->GetInformationObject(0);
  vtkTable* table = vtkTable::SafeDownCast(
    table1Info->Get(vtkDataObject::DATA_OBJECT()));

  // Get output table
  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  vtkTable* output = vtkTable::SafeDownCast(
    outInfo->Get(vtkDataObject::DATA_OBJECT()));

  // Add columns from table, split multiple component columns as necessary
  for (int i = 0; i < table->GetNumberOfColumns(); ++i)
  {
    vtkAbstractArray* col = table->GetColumn(i);
    if (col->GetName() == nullptr)
    {
      vtkWarningMacro("Skipping column with no name!");
      continue;
    }

    int components = col->GetNumberOfComponents();
    if (components == 1)
    {
      output->AddColumn(col);
    }
    else if (components > 1)
    {
      // Split the multicomponent column up into individual columns
      int colSize = col->GetNumberOfTuples();
      for (int j = 0; j < components; ++j)
      {
        const std::string component_label = this->GetComponentLabel(col, j);
        vtkAbstractArray* newCol = vtkAbstractArray::CreateArray(col->GetDataType());
        newCol->SetName(component_label.c_str());
        newCol->SetNumberOfTuples(colSize);
        // pass component name overrides, if provided.
        if (col->HasAComponentName())
        {
          newCol->SetComponentName(0, col->GetComponentName(j));
        }
        // Now copy the components into their new columns
        switch(col->GetDataType())
        {
          vtkExtraExtendedTemplateMacro(
              CopyArrayData(static_cast<VTK_TT*>(col->GetVoidPointer(0)),
                            static_cast<VTK_TT*>(newCol->GetVoidPointer(0)),
                            components, j, colSize));
        }
        if (auto info = newCol->GetInformation())
        {
          info->Set(ORIGINAL_ARRAY_NAME(), col->GetName());
          info->Set(ORIGINAL_COMPONENT_NUMBER(), j);
        }
        output->AddColumn(newCol);
        newCol->Delete();
      }
      // Add a magnitude column and calculate values if requested
      if (this->CalculateMagnitudes && col->IsA("vtkDataArray"))
      {
        std::string component_label = this->GetComponentLabel(col, -1 /* for magnitude */);
        vtkAbstractArray* newCol = vtkAbstractArray::CreateArray(col->GetDataType());
        newCol->SetName(component_label.c_str());
        newCol->SetNumberOfTuples(colSize);
        // Now calculate the magnitude column
        switch(col->GetDataType())
        {
          vtkTemplateMacro(
              CalculateMagnitude(static_cast<VTK_TT*>(col->GetVoidPointer(0)),
                                 static_cast<VTK_TT*>(newCol->GetVoidPointer(0)),
                                 components, colSize));
        }
        if (auto info = newCol->GetInformation())
        {
          info->Set(ORIGINAL_ARRAY_NAME(), col->GetName());
          info->Set(ORIGINAL_COMPONENT_NUMBER(), -1); // for magnitude
        }
        output->AddColumn(newCol);
        newCol->Delete();
      }
    }
  }
  return 1;
}

namespace
{
  //----------------------------------------------------------------------------
  std::string vtkDefaultComponentName(int componentNumber, int componentCount)
  {
    if (componentCount <= 1)
    {
      return "";
    }
    else if (componentNumber == -1)
    {
      return "Magnitude";
    }
    else if (componentCount <= 3 && componentNumber < 3)
    {
      const char* titles[] = {"X", "Y", "Z"};
      return titles[componentNumber];
    }
    else if (componentCount == 6)
    {
      const char* titles[] = {"XX", "YY", "ZZ", "XY", "YZ", "XZ"};
      // Assume this is a symmetric matrix.
      return titles[componentNumber];
    }
    else
    {
      std::ostringstream buffer;
      buffer << componentNumber;
      return buffer.str();
    }
  }
  std::string vtkGetComponentName(vtkAbstractArray* array, int component_no)
  {
    const char* name = array->GetComponentName(component_no);
    if (name)
    {
      return name;
    }
    return vtkDefaultComponentName(component_no, array->GetNumberOfComponents());
  }
};

//---------------------------------------------------------------------------
std::string vtkSplitColumnComponents::GetComponentLabel(vtkAbstractArray* array, int component_no)
{
  std::ostringstream stream;
  switch (this->NamingMode)
  {
  case NUMBERS_WITH_PARENS:
    stream << array->GetName() << " (";
    if (component_no == -1)
    {
      stream << "Magnitude)";
    }
    else
    {
      stream << component_no << ")";
    }
    break;

  case NUMBERS_WITH_UNDERSCORES:
    stream << array->GetName() << "_";
    if (component_no == -1)
    {
      stream << "Magnitude";
    }
    else
    {
      stream << component_no;
    }
    break;

  case NAMES_WITH_PARENS:
    stream << array->GetName() << " ("
      << vtkGetComponentName(array, component_no).c_str() << ")";
    break;

  case NAMES_WITH_UNDERSCORES:
  default:
    stream << array->GetName() << "_"
      << vtkGetComponentName(array, component_no).c_str();
    break;
  }
  return stream.str();
}

//---------------------------------------------------------------------------
void vtkSplitColumnComponents::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "CalculateMagnitudes: " << this->CalculateMagnitudes << endl;
  os << indent << "NamingMode: ";
  switch(this->NamingMode)
  {
  case NAMES_WITH_UNDERSCORES:
    os << "NAMES_WITH_UNDERSCORES" << endl;
    break;
  case NAMES_WITH_PARENS:
    os << "NAMES_WITH_PARENS" << endl;
    break;
  case NUMBERS_WITH_UNDERSCORES:
    os << "NUMBERS_WITH_UNDERSCORES" << endl;
    break;
  case NUMBERS_WITH_PARENS:
    os << "NUMBERS_WITH_PARENS" << endl;
    break;
  default:
    os << "INVALID" << endl;
  }
}
