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

Program:   Visualization Toolkit
Module:    vtkBivariateLinearTableThreshold.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.

=========================================================================*/
/*-------------------------------------------------------------------------
  Copyright 2009 Sandia Corporation.
  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
  the U.S. Government retains certain rights in this software.
-------------------------------------------------------------------------*/
#include "vtkBivariateLinearTableThreshold.h"

#include "vtkDataArrayCollection.h"
#include "vtkDataObject.h"
#include "vtkDoubleArray.h"
#include "vtkIdTypeArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkTable.h"

#include <map>

vtkStandardNewMacro(vtkBivariateLinearTableThreshold);

class vtkBivariateLinearTableThreshold::Internals
{
public:
  std::vector<vtkIdType> ColumnsToThreshold;
  std::vector<vtkIdType> ColumnComponentsToThreshold;
};

vtkBivariateLinearTableThreshold::vtkBivariateLinearTableThreshold()
{
  this->SetNumberOfInputPorts(1);
  this->SetNumberOfOutputPorts(2);

  this->Implementation = new Internals;

  this->Initialize();
}

vtkBivariateLinearTableThreshold::~vtkBivariateLinearTableThreshold()
{
  delete this->Implementation;
}

void vtkBivariateLinearTableThreshold::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  os << "ColumnRanges: " << this->ColumnRanges[0] << " " << this->ColumnRanges[1] << endl;
  os << "UseNormalizedDistance: " << this->UseNormalizedDistance << endl;
  os << "Inclusive: " << this->Inclusive << endl;
  os << "DistanceThreshold: " << this->DistanceThreshold << endl;
  os << "LinearThresholdType: " << this->LinearThresholdType << endl;
}

void vtkBivariateLinearTableThreshold::Initialize()
{
  this->Inclusive = 0;
  this->Implementation->ColumnsToThreshold.clear();
  this->Implementation->ColumnComponentsToThreshold.clear();

  this->DistanceThreshold = 1.0;
  this->ColumnRanges[0] = 1.0;
  this->ColumnRanges[1] = 1.0;
  this->UseNormalizedDistance = 0;
  this->NumberOfLineEquations = 0;
  this->LinearThresholdType = BLT_NEAR;

  this->LineEquations = vtkSmartPointer<vtkDoubleArray>::New();
  this->LineEquations->SetNumberOfComponents(3);
  this->Modified();
}

int vtkBivariateLinearTableThreshold::RequestData(vtkInformation* vtkNotUsed(request),
                                              vtkInformationVector** inputVector,
                                              vtkInformationVector* outputVector)
{
  vtkTable* inTable = vtkTable::GetData( inputVector[0], 0);
  vtkTable* outRowIdsTable = vtkTable::GetData( outputVector, OUTPUT_ROW_IDS );
  vtkTable* outRowDataTable = vtkTable::GetData( outputVector, OUTPUT_ROW_DATA );

  if (!inTable || this->GetNumberOfColumnsToThreshold() != 2)
  {
    return 1;
  }

  if (!outRowIdsTable)
  {
    vtkErrorMacro(<<"No output table, for some reason.");
    return 0;
  }

  vtkSmartPointer<vtkIdTypeArray> outIds = vtkSmartPointer<vtkIdTypeArray>::New();
  if (!ApplyThreshold(inTable,outIds))
  {
    vtkErrorMacro(<<"Error during threshold application.");
    return 0;
  }

  outRowIdsTable->Initialize();
  outRowIdsTable->AddColumn(outIds);

  outRowDataTable->Initialize();
  vtkIdType numColumns = inTable->GetNumberOfColumns();
  for (vtkIdType i=0; i<numColumns; i++)
  {
    vtkDataArray* a = vtkDataArray::CreateDataArray(inTable->GetColumn(i)->GetDataType());
    a->SetNumberOfComponents(inTable->GetColumn(i)->GetNumberOfComponents());
    a->SetName(inTable->GetColumn(i)->GetName());
    outRowDataTable->AddColumn(a);
    a->Delete();
  }

  for (vtkIdType i=0; i<outIds->GetNumberOfTuples(); i++)
  {
    outRowDataTable->InsertNextRow(inTable->GetRow(outIds->GetValue(i)));
  }

  return 1;
}

int vtkBivariateLinearTableThreshold::FillInputPortInformation( int port, vtkInformation* info )
{
  if (port == 0)
  {
    info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkTable");
    return 1;
  }

  return 0;
}

int vtkBivariateLinearTableThreshold::FillOutputPortInformation( int port, vtkInformation* info )
{
  if ( port == OUTPUT_ROW_IDS ||
       port == OUTPUT_ROW_DATA)
  {
    info->Set( vtkDataObject::DATA_TYPE_NAME(), "vtkTable" );
    return 1;
  }

  return 0;
}

void vtkBivariateLinearTableThreshold::AddColumnToThreshold(vtkIdType column, vtkIdType component)
{
  this->Implementation->ColumnsToThreshold.push_back(column);
  this->Implementation->ColumnComponentsToThreshold.push_back(component);
  this->Modified();
}

int vtkBivariateLinearTableThreshold::GetNumberOfColumnsToThreshold()
{
  return (int)this->Implementation->ColumnsToThreshold.size();
}

void vtkBivariateLinearTableThreshold::GetColumnToThreshold(vtkIdType idx, vtkIdType& column, vtkIdType& component)
{
  if (idx < 0 || idx >= (int)this->Implementation->ColumnsToThreshold.size())
  {
    column = -1;
    component = -1;
  }
  else
  {
    column = this->Implementation->ColumnsToThreshold[idx];
    component = this->Implementation->ColumnComponentsToThreshold[idx];
  }
}

void vtkBivariateLinearTableThreshold::ClearColumnsToThreshold()
{
  this->Implementation->ColumnsToThreshold.clear();
  this->Implementation->ColumnComponentsToThreshold.clear();
}

vtkIdTypeArray* vtkBivariateLinearTableThreshold::GetSelectedRowIds(int selection)
{
  vtkTable* table = vtkTable::SafeDownCast(this->GetOutput());

  if (!table)
    return nullptr;

  return vtkArrayDownCast<vtkIdTypeArray>(table->GetColumn(selection));
}

int vtkBivariateLinearTableThreshold::ApplyThreshold(vtkTable* tableToThreshold, vtkIdTypeArray* acceptedIds)
{
  // grab the first two arrays (and their components) to threshold
  vtkIdType column1,column2,component1,component2;

  if (this->GetNumberOfColumnsToThreshold() != 2)
  {
    vtkErrorMacro(<<"This threshold only works on two columns at a time.  Received: "<<this->GetNumberOfColumnsToThreshold());
    return 0;
  }

  this->GetColumnToThreshold(0,column1,component1);
  this->GetColumnToThreshold(1,column2,component2);

  vtkDataArray* a1 = vtkArrayDownCast<vtkDataArray>(tableToThreshold->GetColumn(column1));
  vtkDataArray* a2 = vtkArrayDownCast<vtkDataArray>(tableToThreshold->GetColumn(column2));

  if (!a1 || !a2)
  {
    vtkErrorMacro(<<"Wrong number of arrays received.");
    return 0;
  }

  if (a1->GetNumberOfTuples() != a2->GetNumberOfTuples())
  {
    vtkErrorMacro(<<"Two arrays to threshold must have the same number of tuples.");
    return 0;
  }

  int (vtkBivariateLinearTableThreshold::*thresholdFunc)(double,double) = nullptr;
  switch (this->LinearThresholdType)
  {
    case vtkBivariateLinearTableThreshold::BLT_ABOVE:
      thresholdFunc = &vtkBivariateLinearTableThreshold::ThresholdAbove;
      break;
    case vtkBivariateLinearTableThreshold::BLT_BELOW:
      thresholdFunc = &vtkBivariateLinearTableThreshold::ThresholdBelow;
      break;
    case vtkBivariateLinearTableThreshold::BLT_NEAR:
      thresholdFunc = &vtkBivariateLinearTableThreshold::ThresholdNear;
      break;
    case vtkBivariateLinearTableThreshold::BLT_BETWEEN:
      thresholdFunc = &vtkBivariateLinearTableThreshold::ThresholdBetween;
      break;
    default:
      vtkErrorMacro(<<"Threshold type not defined: "<<this->LinearThresholdType);
      return 0;
  }

  acceptedIds->Initialize();
  vtkIdType numTuples = a1->GetNumberOfTuples();
  double v1,v2;
  for (int i=0; i<numTuples; i++)
  {
    v1 = a1->GetComponent(i,component1);
    v2 = a2->GetComponent(i,component2);

    if ((this->*thresholdFunc)(v1,v2))
    {
      acceptedIds->InsertNextValue(i);
    }
  }

  return 1;
}

void vtkBivariateLinearTableThreshold::AddLineEquation(double* p1, double* p2)
{
  double a = p1[1]-p2[1];
  double b = p2[0]-p1[0];
  double c = p1[0]*p2[1]-p2[0]*p1[1];

  this->AddLineEquation(a,b,c);
}

void vtkBivariateLinearTableThreshold::AddLineEquation(double* p, double slope)
{
  double p2[2] = {p[0]+1.0,p[1]+slope};
  this->AddLineEquation(p,p2);
}

void vtkBivariateLinearTableThreshold::AddLineEquation(double a, double b, double c)
{
  double norm = sqrt(a*a+b*b);
  a /= norm;
  b /= norm;
  c /= norm;

  this->LineEquations->InsertNextTuple3(a,b,c);
  this->NumberOfLineEquations++;
}

void vtkBivariateLinearTableThreshold::ClearLineEquations()
{
  this->LineEquations->Initialize();
  this->NumberOfLineEquations=0;
}

void vtkBivariateLinearTableThreshold::ComputeImplicitLineFunction(double* p1, double* p2, double* abc)
{
  abc[0] = p1[1]-p2[1];
  abc[1] = p2[0]-p1[0];
  abc[2] = p1[0]*p2[1]-p2[0]*p1[1];
}

void vtkBivariateLinearTableThreshold::ComputeImplicitLineFunction(double* p, double slope, double* abc)
{
  double p2[2] = {p[0]+1.0,p[1]+slope};
  vtkBivariateLinearTableThreshold::ComputeImplicitLineFunction(p,p2,abc);
}

int vtkBivariateLinearTableThreshold::ThresholdAbove(double x, double y)
{
  double* c,v;
  for (int i=0; i<this->NumberOfLineEquations; i++)
  {
    c = this->LineEquations->GetTuple3(i);
    v = c[0]*x + c[1]*y + c[2];

    if ((this->GetInclusive() && v >= 0) ||
        (!this->GetInclusive() && v > 0))
    {
      return 1;
    }
  }
  return 0;
}

int vtkBivariateLinearTableThreshold::ThresholdBelow(double x, double y)
{
  double* c,v;
  for (int i=0; i<this->NumberOfLineEquations; i++)
  {
    c = this->LineEquations->GetTuple3(i);
    v = c[0]*x + c[1]*y + c[2];

    if ((this->GetInclusive() && v <= 0) ||
        (!this->GetInclusive() && v < 0))
    {
      return 1;
    }
  }
  return 0;
}

int vtkBivariateLinearTableThreshold::ThresholdNear(double x, double y)
{
  double* c,v;
  for (int i=0; i<this->NumberOfLineEquations; i++)
  {
    c = this->LineEquations->GetTuple3(i);

    if (this->UseNormalizedDistance)
    {
      double dx = fabs(x-(-c[1]*y-c[2])/c[0]);
      double dy = fabs(y-(-c[0]*x-c[2])/c[1]);

      double dxn = dx/this->ColumnRanges[0];
      double dyn = dy/this->ColumnRanges[1];

      v = sqrt(dxn*dxn+dyn*dyn);
    }
    else
    {
      v = fabs(c[0]*x + c[1]*y + c[2]);
    }

    if ((this->GetInclusive() && v <= this->DistanceThreshold) ||
        (!this->GetInclusive() && v < this->DistanceThreshold))
    {
      return 1;
    }
  }

  return 0;
}

int vtkBivariateLinearTableThreshold::ThresholdBetween(double x, double y)
{
  return (this->ThresholdAbove(x,y) && this->ThresholdBelow(x,y));
}
