// -*- c++ -*-
/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkTemporalRanges.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 (c) Sandia Corporation
 See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details.
----------------------------------------------------------------------------*/

#include "vtkTemporalRanges.h"

#include "vtkCellData.h"
#include "vtkCompositeDataIterator.h"
#include "vtkCompositeDataSet.h"
#include "vtkDataArray.h"
#include "vtkDataSet.h"
#include "vtkDoubleArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkStringArray.h"
#include "vtkTable.h"
#include "vtkTypeTraits.h"

#include "vtkSmartPointer.h"
#define VTK_CREATE(type, name) \
  vtkSmartPointer<type> name = vtkSmartPointer<type>::New()

#include <vtkstd/algorithm>
#include <vtkstd/vector>
#include <vtksys/ios/sstream>

#include <math.h>

//=============================================================================
// This is not super portable.  We really should be using vtkMath::IsNan.  The
// only reason we are not is so that we can copy this code to the 3.6 branch of
// ParaView where that method does not yet exist.  Once we move past that, this
// section should go away and all instances of isnan should be replaced with
// vtkMath::IsNan.
#ifndef isnan
// This is compiler specific not platform specific: MinGW doesn't need that.
# if defined(_MSC_VER) || defined(__BORLANDC__)
#  include <float.h>
#  define isnan(x) _isnan(x)
# endif
#endif

//=============================================================================
namespace vtkTemporalRangesNamespace{
  const int AVERAGE_ROW = vtkTemporalRanges::AVERAGE_ROW;
  const int MINIMUM_ROW = vtkTemporalRanges::MINIMUM_ROW;
  const int MAXIMUM_ROW = vtkTemporalRanges::MAXIMUM_ROW;
  const int COUNT_ROW   = vtkTemporalRanges::COUNT_ROW;
  const int NUMBER_OF_ROWS = vtkTemporalRanges::NUMBER_OF_ROWS;

  inline void InitializeColumn(vtkDoubleArray *column)
  {
    column->SetNumberOfComponents(1);
    column->SetNumberOfTuples(NUMBER_OF_ROWS);

    column->SetValue(AVERAGE_ROW, 0.0);
    column->SetValue(MINIMUM_ROW, vtkTypeTraits<double>::Max());
    column->SetValue(MAXIMUM_ROW, vtkTypeTraits<double>::Min());
    column->SetValue(COUNT_ROW,   0.0);
  }

  inline void AccumulateValue(double value, vtkDoubleArray *column)
  {
    if (!isnan(value))
      {
      column->SetValue(AVERAGE_ROW, value + column->GetValue(AVERAGE_ROW));
      column->SetValue(MINIMUM_ROW, vtkstd::min(column->GetValue(MINIMUM_ROW),
                                                value));
      column->SetValue(MAXIMUM_ROW, vtkstd::max(column->GetValue(MAXIMUM_ROW),
                                                value));
      column->SetValue(COUNT_ROW, column->GetValue(COUNT_ROW) + 1);
      }
  }

  inline void AccumulateColumn(vtkDoubleArray *source,
                               vtkDoubleArray *target)
  {
    double targetCount = target->GetValue(COUNT_ROW);
    double sourceCount = source->GetValue(COUNT_ROW);
    double totalCount = targetCount + sourceCount;
    double targetTotal = targetCount*target->GetValue(AVERAGE_ROW);
    double sourceTotal = sourceCount*source->GetValue(AVERAGE_ROW);
    target->SetValue(AVERAGE_ROW, (targetTotal + sourceTotal)/totalCount);
    target->SetValue(MINIMUM_ROW, vtkstd::min(source->GetValue(MINIMUM_ROW),
                                              target->GetValue(MINIMUM_ROW)));
    target->SetValue(MAXIMUM_ROW, vtkstd::max(source->GetValue(MAXIMUM_ROW),
                                              target->GetValue(MAXIMUM_ROW)));
    target->SetValue(COUNT_ROW, totalCount);
  }
};
using namespace vtkTemporalRangesNamespace;

//=============================================================================
vtkCxxRevisionMacro(vtkTemporalRanges, "1.3");
vtkStandardNewMacro(vtkTemporalRanges);

//-----------------------------------------------------------------------------
vtkTemporalRanges::vtkTemporalRanges()
{
  this->CurrentTimeIndex = 0;
}

vtkTemporalRanges::~vtkTemporalRanges()
{
}

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

//-----------------------------------------------------------------------------
int vtkTemporalRanges::FillInputPortInformation(int port, vtkInformation *info)
{
  if (port == 0)
    {
    info->Remove(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE());
    info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet");
    info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkCompositeDataSet");
    return 1;
    }

  return 0;
}

//-----------------------------------------------------------------------------
int vtkTemporalRanges::RequestInformation(
                                 vtkInformation *vtkNotUsed(request),
                                 vtkInformationVector **vtkNotUsed(inputVector),
                                 vtkInformationVector *outputVector)
{
  vtkInformation *outInfo = outputVector->GetInformationObject(0);

  // The output data of this filter has no time associated with it.  It is the
  // result of computations that happen over all time.
  outInfo->Remove(vtkStreamingDemandDrivenPipeline::TIME_STEPS());
  outInfo->Remove(vtkStreamingDemandDrivenPipeline::TIME_RANGE());

  return 1;
}

//-----------------------------------------------------------------------------
int vtkTemporalRanges::RequestUpdateExtent(
                                 vtkInformation *vtkNotUsed(request),
                                 vtkInformationVector **inputVector,
                                 vtkInformationVector *vtkNotUsed(outputVector))
{
  vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);

  // The RequestData method will tell the pipeline executive to iterate the
  // upstream pipeline to get each time step in order.  The executive in turn
  // will call this method to get the extent request for each iteration (in this
  // case the time step).
  double *inTimes = inInfo->Get(vtkStreamingDemandDrivenPipeline::TIME_STEPS());
  if (inTimes)
    {
    inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEPS(),
                &inTimes[this->CurrentTimeIndex], 1);
    }

  return 1;
}

//-----------------------------------------------------------------------------
int vtkTemporalRanges::RequestData(vtkInformation *request,
                                   vtkInformationVector **inputVector,
                                   vtkInformationVector *outputVector)
{
  vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
  vtkTable *output = vtkTable::GetData(outputVector);

  if (this->CurrentTimeIndex == 0)
    {
    // First execution.  Initialize table.
    this->InitializeTable(output);
    }

  vtkCompositeDataSet *compositeInput = vtkCompositeDataSet::GetData(inInfo);
  vtkDataSet *dsInput = vtkDataSet::GetData(inInfo);

  if (compositeInput)
    {
    this->AccumulateCompositeData(compositeInput, output);
    }
  else if (dsInput)
    {
    this->AccumulateDataSet(dsInput, output);
    }
  else
    {
    vtkWarningMacro(<< "Unknown data type : "
                    << vtkDataObject::GetData(inputVector[0])->GetClassName());
    return 0;
    }

  this->CurrentTimeIndex++;

  if (  this->CurrentTimeIndex
      < inInfo->Length(vtkStreamingDemandDrivenPipeline::TIME_STEPS()) )
    {
    // There is still more to do.
    request->Set(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING(), 1);
    }
  else
    {
    // We are done.  Finish up.
    request->Remove(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING());
    this->CurrentTimeIndex = 0;
    }

  return 1;
}

//-----------------------------------------------------------------------------
void vtkTemporalRanges::InitializeTable(vtkTable *output)
{
  output->Initialize();

  VTK_CREATE(vtkStringArray, rangeName);
  rangeName->SetName("Range Name");
  rangeName->SetNumberOfComponents(1);
  rangeName->SetNumberOfTuples(NUMBER_OF_ROWS);

  rangeName->SetValue(AVERAGE_ROW, "Average");
  rangeName->SetValue(MINIMUM_ROW, "Minimum");
  rangeName->SetValue(MAXIMUM_ROW, "Maximum");
  rangeName->SetValue(COUNT_ROW, "Count");

  output->AddColumn(rangeName);
}

//-----------------------------------------------------------------------------
void vtkTemporalRanges::AccumulateCompositeData(vtkCompositeDataSet *input,
                                                vtkTable *output)
{
  vtkSmartPointer<vtkCompositeDataIterator> iter;
  iter.TakeReference(input->NewIterator());
  for (iter->InitTraversal(); !iter->IsDoneWithTraversal();
       iter->GoToNextItem())
    {
    vtkDataObject *dataobj = iter->GetCurrentDataObject();
    vtkDataSet *dataset = vtkDataSet::SafeDownCast(dataobj);
    if (dataset)
      {
      this->AccumulateDataSet(dataset, output);
      }
    else if (dataobj)
      {
      vtkWarningMacro(<< "Unknown data type : " << dataobj->GetClassName());
      }
    }
}

//-----------------------------------------------------------------------------
void vtkTemporalRanges::AccumulateDataSet(vtkDataSet *input, vtkTable *output)
{
  this->AccumulateFields(input->GetPointData(), output);
  this->AccumulateFields(input->GetCellData(), output);
  this->AccumulateFields(input->GetFieldData(), output);
}

//-----------------------------------------------------------------------------
void vtkTemporalRanges::AccumulateFields(vtkFieldData *fields, vtkTable *output)
{
  for (int i = 0; i < fields->GetNumberOfArrays(); i++)
    {
    vtkDataArray *array = fields->GetArray(i);
    if (array)
      {
      this->AccumulateArray(array, output);
      }
    }
}

//-----------------------------------------------------------------------------
void vtkTemporalRanges::AccumulateArray(vtkDataArray *field, vtkTable *output)
{
  int numComponents = field->GetNumberOfComponents();
  vtkIdType numTuples = field->GetNumberOfTuples();
  vtkDoubleArray *magnitudeColumn = NULL;
  vtkstd::vector<vtkDoubleArray *> componentColumns(numComponents);
  VTK_CREATE(vtkDoubleArray, magnitudeAccumulate);
  vtkstd::vector<vtkSmartPointer<vtkDoubleArray> > componentAccumulate(numComponents);
  if (numComponents > 1)
    {
    magnitudeColumn = this->GetColumn(output, field->GetName(), -1);
    InitializeColumn(magnitudeAccumulate);
    for (int i = 0; i < numComponents; i++)
      {
      componentColumns[i] = this->GetColumn(output, field->GetName(), i);
      componentAccumulate[i] = vtkSmartPointer<vtkDoubleArray>::New();
      InitializeColumn(componentAccumulate[i]);
      }
    }
  else
    {
    componentColumns[0] = this->GetColumn(output, field->GetName());
    componentAccumulate[0] = vtkSmartPointer<vtkDoubleArray>::New();
    InitializeColumn(componentAccumulate[0]);
    }

  for (vtkIdType i = 0; i < numTuples; i++)
    {
    double mag = 0.0;
    for (int j = 0; j < numComponents; j++)
      {
      double value = field->GetComponent(i, j);
      mag += value*value;
      AccumulateValue(value, componentAccumulate[j]);
      }
    if (magnitudeColumn)
      {
      mag = sqrt(mag);
      AccumulateValue(mag, magnitudeAccumulate);
      }
    }

  for (int j = 0; j < numComponents; j++)
    {
    componentAccumulate[j]->SetValue(AVERAGE_ROW,
                              (  componentAccumulate[j]->GetValue(AVERAGE_ROW)
                               / componentAccumulate[j]->GetValue(COUNT_ROW) ));
    AccumulateColumn(componentAccumulate[j], componentColumns[j]);
    }
  if (magnitudeColumn)
    {
    magnitudeAccumulate->SetValue(AVERAGE_ROW,
                                  (  magnitudeAccumulate->GetValue(AVERAGE_ROW)
                                   / magnitudeAccumulate->GetValue(COUNT_ROW)));
    AccumulateColumn(magnitudeAccumulate, magnitudeColumn);
    }
}

//-----------------------------------------------------------------------------
void vtkTemporalRanges::AccumulateTable(vtkTable *source, vtkTable *target)
{
  for (vtkIdType c = 0; c < source->GetNumberOfColumns(); c++)
    {
    vtkDoubleArray *sourceColumn
      = vtkDoubleArray::SafeDownCast(source->GetColumn(c));
    if (!sourceColumn) continue;
    vtkDoubleArray *targetColumn
      = this->GetColumn(target, sourceColumn->GetName());
    AccumulateColumn(sourceColumn, targetColumn);
    }
}

//-----------------------------------------------------------------------------
vtkDoubleArray *vtkTemporalRanges::GetColumn(vtkTable *table, const char *name,
                                             int component)
{
  vtksys_ios::ostringstream fullname;
  fullname << name << "_";
  if (component < 0)
    {
    fullname << "M";
    }
  else
    {
    fullname << component;
    }
  vtkstd::string fullnamestring = fullname.str();
  return this->GetColumn(table, fullnamestring.c_str());
}

//-----------------------------------------------------------------------------
vtkDoubleArray *vtkTemporalRanges::GetColumn(vtkTable *table, const char *name)
{
  vtkAbstractArray *abstractArray = table->GetColumnByName(name);
  vtkDoubleArray *array = vtkDoubleArray::SafeDownCast(abstractArray);
  if (!array)
    {
    if (abstractArray)
      {
      table->RemoveColumnByName(name);
      }
    array = vtkDoubleArray::New();
    array->SetName(name);
    InitializeColumn(array);
    table->AddColumn(array);
    array->Delete();    //Reference held by table.
    }

  return array;
}
