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

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

#include "vtkChartParallelCoordinates.h"
#include "vtkContext2D.h"
#include "vtkAxis.h"
#include "vtkPen.h"
#include "vtkFloatArray.h"
#include "vtkDoubleArray.h"
#include "vtkVector.h"
#include "vtkTransform2D.h"
#include "vtkContextDevice2D.h"
#include "vtkContextMapper2D.h"
#include "vtkTable.h"
#include "vtkDataArray.h"
#include "vtkIdTypeArray.h"
#include "vtkStringArray.h"
#include "vtkTimeStamp.h"
#include "vtkInformation.h"
#include "vtkSmartPointer.h"
#include "vtkUnsignedCharArray.h"
#include "vtkLookupTable.h"

// Need to turn some arrays of strings into categories
#include "vtkStringToCategory.h"

#include "vtkObjectFactory.h"

#include <vector>
#include <algorithm>

class vtkPlotParallelCoordinates::Private :
    public std::vector< std::vector<float> >
{
public:
  Private()
  {
    this->SelectionInitialized = false;
  }

  std::vector<float> AxisPos;
  bool SelectionInitialized;
};


//-----------------------------------------------------------------------------
vtkStandardNewMacro(vtkPlotParallelCoordinates)

//-----------------------------------------------------------------------------
vtkPlotParallelCoordinates::vtkPlotParallelCoordinates()
{
  this->Storage = new vtkPlotParallelCoordinates::Private;
  this->Pen->SetColor(0, 0, 0, 25);

  this->LookupTable = 0;
  this->Colors = 0;
  this->ScalarVisibility = 0;
}

//-----------------------------------------------------------------------------
vtkPlotParallelCoordinates::~vtkPlotParallelCoordinates()
{
  delete this->Storage;
  if (this->LookupTable)
  {
    this->LookupTable->UnRegister(this);
  }
  if ( this->Colors != 0 )
  {
    this->Colors->UnRegister(this);
  }
}

//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::Update()
{
  if (!this->Visible)
  {
    return;
  }
  // Check if we have an input
  vtkTable *table = this->Data->GetInput();
  if (!table)
  {
    vtkDebugMacro(<< "Update event called with no input table set.");
    return;
  }

  this->UpdateTableCache(table);
}

//-----------------------------------------------------------------------------
bool vtkPlotParallelCoordinates::Paint(vtkContext2D *painter)
{
  // This is where everything should be drawn, or dispatched to other methods.
  vtkDebugMacro(<< "Paint event called in vtkPlotParallelCoordinates.");

  if (!this->Visible)
  {
    return false;
  }

  painter->ApplyPen(this->Pen);

  if (this->Storage->size() == 0)
  {
    return false;
  }

  size_t cols = this->Storage->size();
  size_t rows = this->Storage->at(0).size();
  vtkVector2f* line = new vtkVector2f[cols];

  // Update the axis positions
  vtkChartParallelCoordinates *parent =
      vtkChartParallelCoordinates::SafeDownCast(this->Parent);

  for (size_t i = 0; i < cols; ++i)
  {
    this->Storage->AxisPos[i] = parent->GetAxis(int(i)) ?
                                parent->GetAxis(int(i))->GetPoint1()[0] :
                                0;
  }

  vtkIdType selection = 0;
  vtkIdType id = 0;
  vtkIdType selectionSize = 0;
  if (this->Selection)
  {
    selectionSize = this->Selection->GetNumberOfTuples();
    if (selectionSize)
    {
      this->Selection->GetTypedTuple(selection, &id);
    }
  }

  // Draw all of the lines
  painter->ApplyPen(this->Pen);
  int ncComps(0);
  if (this->ScalarVisibility && this->Colors)
  {
    ncComps = static_cast<int>(this->Colors->GetNumberOfComponents());
  }
  if (this->ScalarVisibility && this->Colors && ncComps == 4)
  {
    for (size_t i = 0, nc = 0; i < rows; ++i, nc += ncComps)
    {
      for (size_t j = 0; j < cols; ++j)
      {
        line[j].Set(this->Storage->AxisPos[j], (*this->Storage)[j][i]);
      }
      painter->GetPen()->SetColor(this->Colors->GetPointer(nc));
      painter->DrawPoly(line[0].GetData(), static_cast<int>(cols));
    }
  }
  else
  {
    for (size_t i = 0; i < rows; ++i)
    {
      for (size_t j = 0; j < cols; ++j)
      {
        line[j].Set(this->Storage->AxisPos[j], (*this->Storage)[j][i]);
      }
      painter->DrawPoly(line[0].GetData(), static_cast<int>(cols));
    }
  }

  // Now draw the selected lines
  if (this->Selection)
  {
    painter->GetPen()->SetColor(255, 0, 0, 100);
    for (vtkIdType i = 0; i < this->Selection->GetNumberOfTuples(); ++i)
    {
      for (size_t j = 0; j < cols; ++j)
      {
        this->Selection->GetTypedTuple(i, &id);
        line[j].Set(this->Storage->AxisPos[j], (*this->Storage)[j][id]);
      }
      painter->DrawPoly(line[0].GetData(), static_cast<int>(cols));
    }
  }

  delete[] line;

  return true;
}

//-----------------------------------------------------------------------------
bool vtkPlotParallelCoordinates::PaintLegend(vtkContext2D *painter,
                                             const vtkRectf& rect, int)
{
  painter->ApplyPen(this->Pen);
  painter->DrawLine(rect[0]          , rect[1] + 0.5 * rect[3],
                    rect[0] + rect[2], rect[1] + 0.5 * rect[3]);
  return true;
}

//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::GetBounds(double *)
{

}

//-----------------------------------------------------------------------------
bool vtkPlotParallelCoordinates::SetSelectionRange(int axis, float low,
                                                   float high)
{
  if (!this->Selection)
  {
    this->Storage->SelectionInitialized = false;
    this->Selection = vtkIdTypeArray::New();
  }

  if (this->Storage->SelectionInitialized)
  {
    // Further refine the selection that has already been made
    vtkIdTypeArray *array = vtkIdTypeArray::New();
    std::vector<float>& col = this->Storage->at(axis);
    for (vtkIdType i = 0; i < this->Selection->GetNumberOfTuples(); ++i)
    {
      vtkIdType id = 0;
      this->Selection->GetTypedTuple(i, &id);
      if (col[id] >= low && col[id] <= high)
      {
        // Remove this point - no longer selected
        array->InsertNextValue(id);
      }
    }
    this->Selection->DeepCopy(array);
    array->Delete();
  }
  else
  {
    // First run - ensure the selection list is empty and build it up
    std::vector<float>& col = this->Storage->at(axis);
    for (size_t i = 0; i < col.size(); ++i)
    {
      if (col[i] >= low && col[i] <= high)
      {
        // Remove this point - no longer selected
        this->Selection->InsertNextValue(i);
      }
    }
    this->Storage->SelectionInitialized = true;
  }
  return true;
}

//-----------------------------------------------------------------------------
bool vtkPlotParallelCoordinates::ResetSelectionRange()
{
  this->Storage->SelectionInitialized = false;
  if (this->Selection)
  {
    this->Selection->SetNumberOfTuples(0);
  }
  return true;
}

//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::SetInputData(vtkTable* table)
{
  if (table == this->Data->GetInput() && (!table ||
                                          table->GetMTime() < this->BuildTime))
  {
    return;
  }

  bool updateVisibility = table != this->Data->GetInput();
  this->vtkPlot::SetInputData(table);
  vtkChartParallelCoordinates *parent =
      vtkChartParallelCoordinates::SafeDownCast(this->Parent);

  if (parent && table && updateVisibility)
  {
    parent->SetColumnVisibilityAll(false);
    // By default make the first 10 columns visible in a plot.
    for (vtkIdType i = 0; i < table->GetNumberOfColumns() && i < 10; ++i)
    {
      parent->SetColumnVisibility(table->GetColumnName(i), true);
    }
  }
  else if (parent && updateVisibility)
  {
    // No table, therefore no visible columns
    parent->GetVisibleColumns()->SetNumberOfTuples(0);
  }
}

//-----------------------------------------------------------------------------
bool vtkPlotParallelCoordinates::UpdateTableCache(vtkTable *table)
{
  // Each axis is a column in our storage array, they are scaled from 0.0 to 1.0
  vtkChartParallelCoordinates *parent =
      vtkChartParallelCoordinates::SafeDownCast(this->Parent);
  if (!parent || !table || table->GetNumberOfColumns() == 0)
  {
    return false;
  }

  vtkStringArray* cols = parent->GetVisibleColumns();

  this->Storage->resize(cols->GetNumberOfTuples());
  this->Storage->AxisPos.resize(cols->GetNumberOfTuples());
  vtkIdType rows = table->GetNumberOfRows();

  for (vtkIdType i = 0; i < cols->GetNumberOfTuples(); ++i)
  {
    std::vector<float>& col = this->Storage->at(i);
    vtkAxis* axis = parent->GetAxis(i);
    col.resize(rows);
    vtkSmartPointer<vtkDataArray> data =
        vtkArrayDownCast<vtkDataArray>(table->GetColumnByName(cols->GetValue(i)));
    if (!data)
    {
      if (table->GetColumnByName(cols->GetValue(i))->IsA("vtkStringArray"))
      {
        // We have a different kind of column - attempt to make it into an enum
        vtkStringToCategory* stoc = vtkStringToCategory::New();
        stoc->SetInputData(table);
        stoc->SetInputArrayToProcess(0, 0, 0,
                                     vtkDataObject::FIELD_ASSOCIATION_ROWS,
                                     cols->GetValue(i));
        stoc->SetCategoryArrayName("enumPC");
        stoc->Update();
        vtkTable* table2 = vtkTable::SafeDownCast(stoc->GetOutput());
        vtkTable* stringTable = vtkTable::SafeDownCast(stoc->GetOutput(1));
        if (table2)
        {
          data = vtkArrayDownCast<vtkDataArray>(table2->GetColumnByName("enumPC"));
        }
        if (stringTable && stringTable->GetColumnByName("Strings"))
        {
          vtkStringArray* strings =
              vtkArrayDownCast<vtkStringArray>(stringTable->GetColumnByName("Strings"));
          vtkSmartPointer<vtkDoubleArray> arr =
              vtkSmartPointer<vtkDoubleArray>::New();
          for (vtkIdType j = 0; j < strings->GetNumberOfTuples(); ++j)
          {
            arr->InsertNextValue(j);
          }
          // Now we need to set the range on the string axis
          axis->SetCustomTickPositions(arr, strings);
          if (strings->GetNumberOfTuples() > 1)
          {
            axis->SetUnscaledRange(0.0, strings->GetNumberOfTuples()-1);
          }
          else
          {
            axis->SetUnscaledRange(-0.1, 0.1);
          }
          axis->Update();
        }
        stoc->Delete();
      }
      // If we still don't have a valid data array then skip this column.
      if (!data)
      {
        continue;
      }
    }

    // Also need the range from the appropriate axis, to normalize points
    double min = axis->GetUnscaledMinimum();
    double max = axis->GetUnscaledMaximum();
    double scale = 1.0f / (max - min);

    for (vtkIdType j = 0; j < rows; ++j)
    {
      col[j] = (data->GetTuple1(j) - min) * scale;
    }
  }

  // Additions for color mapping
  if (this->ScalarVisibility && !this->ColorArrayName.empty())
  {
    vtkDataArray* c =
      vtkArrayDownCast<vtkDataArray>(table->GetColumnByName(this->ColorArrayName));
    // TODO: Should add support for categorical coloring & try enum lookup
    if (this->Colors)
    {
      this->Colors->UnRegister(this);
      this->Colors = 0;
    }
    if (c)
    {
      if (!this->LookupTable)
      {
        this->CreateDefaultLookupTable();
      }
      this->Colors = this->LookupTable->MapScalars(c, VTK_COLOR_MODE_MAP_SCALARS, -1);
      // Consistent register and unregisters
      this->Colors->Register(this);
      this->Colors->Delete();
    }
  }

  this->BuildTime.Modified();
  return true;
}

//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::SetLookupTable(vtkScalarsToColors *lut)
{
  if ( this->LookupTable != lut )
  {
    if ( this->LookupTable)
    {
      this->LookupTable->UnRegister(this);
    }
    this->LookupTable = lut;
    if (lut)
    {
      lut->Register(this);
    }
    this->Modified();
  }
}

//-----------------------------------------------------------------------------
vtkScalarsToColors *vtkPlotParallelCoordinates::GetLookupTable()
{
  if ( this->LookupTable == 0 )
  {
    this->CreateDefaultLookupTable();
  }
  return this->LookupTable;
}

//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::CreateDefaultLookupTable()
{
  if ( this->LookupTable)
  {
    this->LookupTable->UnRegister(this);
  }
  this->LookupTable = vtkLookupTable::New();
  // Consistent Register/UnRegisters.
  this->LookupTable->Register(this);
  this->LookupTable->Delete();
}

//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::SelectColorArray(const vtkStdString &arrayName)
{
  vtkTable *table = this->Data->GetInput();
  if (!table)
  {
    vtkDebugMacro(<< "SelectColorArray called with no input table set.");
    return;
  }
  if (this->ColorArrayName == arrayName)
  {
    return;
  }
  for (vtkIdType c = 0; c < table->GetNumberOfColumns(); ++c)
  {
    if (table->GetColumnName(c) == arrayName)
    {
      this->ColorArrayName = arrayName;
      this->Modified();
      return;
    }
  }
  vtkDebugMacro(<< "SelectColorArray called with invalid column name.");
  this->ColorArrayName = "";
  this->Modified();
  return;
}

//-----------------------------------------------------------------------------
vtkStdString vtkPlotParallelCoordinates::GetColorArrayName()
{
  return this->ColorArrayName;
}

//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::SelectColorArray(vtkIdType arrayNum)
{
  vtkTable *table = this->Data->GetInput();
  if (!table)
  {
    vtkDebugMacro(<< "SelectColorArray called with no input table set.");
    return;
  }
  vtkDataArray *col = vtkArrayDownCast<vtkDataArray>(table->GetColumn(arrayNum));
  // TODO: Should add support for categorical coloring & try enum lookup
  if (!col)
  {
    vtkDebugMacro(<< "SelectColorArray called with invalid column index");
    return;
  }
  else
  {
    if (this->ColorArrayName == table->GetColumnName(arrayNum))
    {
      return;
    }
    else
    {
      this->ColorArrayName = table->GetColumnName(arrayNum);
      this->Modified();
    }
  }
}

//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::PrintSelf(ostream &os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}
