#include "vtkKMeansDistanceFunctor.h"

#include "vtkDoubleArray.h"
#include "vtkIdTypeArray.h"
#include "vtkIntArray.h"
#include "vtkObjectFactory.h"
#include "vtkTable.h"
#include "vtkVariantArray.h"

vtkStandardNewMacro(vtkKMeansDistanceFunctor);

// ----------------------------------------------------------------------
vtkKMeansDistanceFunctor::vtkKMeansDistanceFunctor()
{
  this->EmptyTuple = vtkVariantArray::New();
}

// ----------------------------------------------------------------------
vtkKMeansDistanceFunctor::~vtkKMeansDistanceFunctor()
{
  this->EmptyTuple->Delete();
}

// ----------------------------------------------------------------------
void vtkKMeansDistanceFunctor::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "EmptyTuple: " << this->EmptyTuple << "\n";
}

// ----------------------------------------------------------------------
vtkVariantArray* vtkKMeansDistanceFunctor::GetEmptyTuple(vtkIdType dimension)
{
  if (this->EmptyTuple->GetNumberOfValues() != dimension)
  {
    this->EmptyTuple->SetNumberOfValues(dimension);
    for (vtkIdType i = 0; i < dimension; ++i)
    {
      this->EmptyTuple->SetValue(i, 0.0);
    }
  }
  return this->EmptyTuple;
}

// ----------------------------------------------------------------------
void vtkKMeansDistanceFunctor::operator()(
  double& distance, vtkVariantArray* clusterCoord, vtkVariantArray* dataCoord)
{
  distance = 0.0;
  if (clusterCoord->GetNumberOfValues() != dataCoord->GetNumberOfValues())
  {
    cout << "The dimensions of the cluster and data do not match." << endl;
    distance = -1;
  }

  for (vtkIdType i = 0; i < clusterCoord->GetNumberOfValues(); i++)
  {
    distance += (clusterCoord->GetValue(i).ToDouble() - dataCoord->GetValue(i).ToDouble()) *
      (clusterCoord->GetValue(i).ToDouble() - dataCoord->GetValue(i).ToDouble());
  }
}

// ----------------------------------------------------------------------
void vtkKMeansDistanceFunctor::PairwiseUpdate(vtkTable* clusterCoords, vtkIdType rowIndex,
  vtkVariantArray* dataCoord, vtkIdType dataCoordCardinality, vtkIdType totalCardinality)
{
  if (clusterCoords->GetNumberOfColumns() != dataCoord->GetNumberOfValues())
  {
    cout << "The dimensions of the cluster and/or data do not match." << endl;
    return;
  }

  if (totalCardinality > 0)
  {
    for (vtkIdType i = 0; i < clusterCoords->GetNumberOfColumns(); ++i)
    {
      double curCoord = clusterCoords->GetValue(rowIndex, i).ToDouble();
      clusterCoords->SetValue(rowIndex, i,
        curCoord +
          static_cast<double>(dataCoordCardinality) *
            (dataCoord->GetValue(i).ToDouble() - curCoord) / static_cast<double>(totalCardinality));
    }
  }
}

// ----------------------------------------------------------------------
void vtkKMeansDistanceFunctor::PerturbElement(vtkTable* newClusterElements,
  vtkTable* curClusterElements, vtkIdType changeID, vtkIdType startRunID, vtkIdType endRunID,
  double alpha)
{
  double numInRange = static_cast<double>(endRunID - startRunID);
  vtkIdType dimension = newClusterElements->GetNumberOfColumns();
  std::vector<double> perturbedValues(dimension);

  for (vtkIdType i = startRunID; i < endRunID; i++)
  {
    for (vtkIdType j = 0; j < dimension; j++)
    {
      if (i == changeID)
      {
        perturbedValues[j] = alpha * curClusterElements->GetValue(i, j).ToDouble();
      }
      else
      {
        if (numInRange > 1.0)
        {
          perturbedValues[j] =
            (1.0 - alpha) / (numInRange - 1.0) * curClusterElements->GetValue(i, j).ToDouble();
        }
        else
        {
          perturbedValues[j] =
            (1.0 - alpha) / (numInRange)*curClusterElements->GetValue(i, j).ToDouble();
        }
      }
    }
  }
}

// ----------------------------------------------------------------------
void* vtkKMeansDistanceFunctor::AllocateElementArray(vtkIdType size)
{
  return new double[size];
}

// ----------------------------------------------------------------------
void vtkKMeansDistanceFunctor::DeallocateElementArray(void* array)
{
  delete[] static_cast<double*>(array);
}

// ----------------------------------------------------------------------
vtkAbstractArray* vtkKMeansDistanceFunctor::CreateCoordinateArray()
{
  return vtkDoubleArray::New();
}

// ----------------------------------------------------------------------
void vtkKMeansDistanceFunctor::PackElements(vtkTable* curTable, void* vElements)
{
  vtkIdType numCols = curTable->GetNumberOfColumns();
  vtkIdType numRows = curTable->GetNumberOfRows();
  double* localElements = static_cast<double*>(vElements);

  for (vtkIdType col = 0; col < numCols; col++)
  {
    vtkDoubleArray* doubleArr = vtkArrayDownCast<vtkDoubleArray>(curTable->GetColumn(col));
    memcpy(&(localElements[col * numRows]), doubleArr->GetPointer(0), numRows * sizeof(double));
  }
}

// ----------------------------------------------------------------------
void vtkKMeansDistanceFunctor::UnPackElements(
  vtkTable* curTable, void* vLocalElements, vtkIdType numRows, vtkIdType numCols)
{
  double* localElements = static_cast<double*>(vLocalElements);
  for (vtkIdType i = 0; i < numRows; i++)
  {
    vtkVariantArray* curRow = vtkVariantArray::New();
    for (int j = 0; j < numCols; j++)
    {
      curRow->InsertNextValue(localElements[j * numRows + i]);
    }
    curTable->InsertNextRow(curRow);
    curRow->Delete();
  }
}

// ----------------------------------------------------------------------
void vtkKMeansDistanceFunctor::UnPackElements(
  vtkTable* curTable, vtkTable* newTable, void* vLocalElements, void* vGlobalElements, int np)
{
  double* globalElements = static_cast<double*>(vGlobalElements);
  double* localElements = static_cast<double*>(vLocalElements);
  vtkIdType numCols = curTable->GetNumberOfColumns();
  vtkIdType numRows = curTable->GetNumberOfRows();
  vtkIdType numElements = numCols * numRows;
  for (vtkIdType col = 0; col < numCols; col++)
  {
    vtkDoubleArray* doubleArr = vtkDoubleArray::New();
    doubleArr->SetName(curTable->GetColumnName(col));
    doubleArr->SetNumberOfComponents(1);
    doubleArr->SetNumberOfTuples(numRows * np);
    for (int j = 0; j < np; j++)
    {
      double* ptr = doubleArr->GetPointer(j * numRows);
      memcpy(ptr, &(globalElements[j * numElements + col * numRows]), numRows * sizeof(double));
    }
    newTable->AddColumn(doubleArr);
    doubleArr->Delete();
  }
  delete[] localElements;
  delete[] globalElements;
}

// ----------------------------------------------------------------------
int vtkKMeansDistanceFunctor::GetDataType()
{
  return VTK_DOUBLE;
}
