/*
 * Copyright 2008 Sandia Corporation.
 * Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive
 * license for use of this work by or on behalf of the
 * U.S. Government. Redistribution and use in source and binary forms, with
 * or without modification, are permitted provided that this Notice and any
 * statement of authorship are reproduced on all copies.
 */
// .SECTION Thanks
// Thanks to Janine Bennett, Philippe Pebay, and David Thompson from Sandia National Laboratories
// for implementing this test.

#include "vtkDoubleArray.h"
#include "vtkMultiBlockDataSet.h"
#include "vtkStringArray.h"
#include "vtkIdTypeArray.h"
#include "vtkTable.h"
#include "vtkMath.h"
#include "vtkKMeansStatistics.h"
#include "vtkStdString.h"
#include "vtkTimerLog.h"

#include <vtksys/ios/sstream>


//=============================================================================
int main( int, char *[] )
{
  int testStatus = 0;

  const int nDim = 4;
  int nVals = 50;

  // Seed random number generator
  vtkMath::RandomSeed( static_cast<int>( vtkTimerLog::GetUniversalTime() ) );

  // Generate an input table that contains samples of mutually independent random variables over [0, 1]
  vtkTable* inputData = vtkTable::New();
  vtkDoubleArray* doubleArray;

  int numComponents = 1;
  for ( int c = 0; c < nDim; ++ c )
    {
    vtksys_ios::ostringstream colName;
    colName << "coord " << c;
    doubleArray = vtkDoubleArray::New();
    doubleArray->SetNumberOfComponents( numComponents );
    doubleArray->SetName( colName.str().c_str() );
    doubleArray->SetNumberOfTuples( nVals );

    double x;
    for ( int r = 0; r < nVals; ++ r )
      {
      //x = vtkMath::Gaussian();
      x = vtkMath::Random();
      doubleArray->SetValue( r, x );
      }

    inputData->AddColumn( doubleArray );
    doubleArray->Delete();
    }

  vtkTable* paramData = vtkTable::New();
  vtkIdTypeArray* paramCluster;
  vtkDoubleArray* paramArray;
  const int numRuns = 5;
  const int numClustersInRun[] = { 5, 2, 3, 4, 5 };
  paramCluster = vtkIdTypeArray::New();
  paramCluster->SetName( "K" );

  for( int curRun = 0; curRun < numRuns; curRun++ )
    {
    for( int nInRun = 0; nInRun < numClustersInRun[curRun]; nInRun++ )
      {
      paramCluster->InsertNextValue( numClustersInRun[curRun] );
      }
    }
  paramData->AddColumn( paramCluster );
  paramCluster->Delete();

  for ( int c = 0; c < 5; ++ c )
    {
    vtksys_ios::ostringstream colName;
    colName << "coord " << c;
    paramArray = vtkDoubleArray::New();
    paramArray->SetNumberOfComponents( numComponents );
    paramArray->SetName( colName.str().c_str() );

    double x;
    for( int curRun = 0; curRun < numRuns; curRun++ )
      {
      for( int nInRun = 0; nInRun < numClustersInRun[curRun]; nInRun++ )
        {
        //x = vtkMath::Gaussian();
        x = vtkMath::Random();
        paramArray->InsertNextValue( x );
        }
      }
    paramData->AddColumn( paramArray );
    paramArray->Delete();
    }

  // Set k-means statistics algorithm and its input data port
  vtkKMeansStatistics* haruspex = vtkKMeansStatistics::New();

  // First verify that absence of input does not cause trouble
  cout << "## Verifying that absence of input does not cause trouble... ";
  haruspex->Update();
  cout << "done.\n";

  // Prepare first test with data
  haruspex->SetInput( vtkStatisticsAlgorithm::INPUT_DATA, inputData );
  haruspex->SetColumnStatus( inputData->GetColumnName( 0 ) , 1 );
  haruspex->SetColumnStatus( inputData->GetColumnName( 2 ) , 1 );
  haruspex->SetColumnStatus( "Testing", 1 );
  haruspex->RequestSelectedColumns();
  haruspex->SetDefaultNumberOfClusters( 3 );

  cout << "## Testing with no input data:"
           << "\n";
  // Test Learn and Derive options
  haruspex->SetLearnOption( true );
  haruspex->SetDeriveOption( true );
  haruspex->SetTestOption( false );
  haruspex->SetAssessOption( false );

  haruspex->Update();
  vtkMultiBlockDataSet* outputMetaDS = vtkMultiBlockDataSet::SafeDownCast(
                        haruspex->GetOutputDataObject( vtkStatisticsAlgorithm::OUTPUT_MODEL ) );
  for ( unsigned int b = 0; b < outputMetaDS->GetNumberOfBlocks(); ++ b )
    {
    vtkTable* outputMeta = vtkTable::SafeDownCast( outputMetaDS->GetBlock( b ) );
    if ( b == 0 )
      {

      vtkIdType testIntValue = 0;
      for( vtkIdType r = 0; r < outputMeta->GetNumberOfRows(); r++ )
        {
        testIntValue += outputMeta->GetValueByName( r, "Cardinality" ).ToInt();
        }

      cout << "## Computed clusters (cardinality: "
           << testIntValue
           << " / run):\n";

      if ( testIntValue != nVals )
        {
        vtkGenericWarningMacro("Sum of cluster cardinalities is incorrect: "
                               << testIntValue
                               << " != "
                               << nVals
                               << ".");
        testStatus = 1;
        }
      }
    else
      {
      cout << "## Ranked cluster: "
           << "\n";
      }

    outputMeta->Dump();
    cout << "\n";
    }


  haruspex->SetInput( vtkStatisticsAlgorithm::LEARN_PARAMETERS, paramData );
  cout << "## Testing with input table:"
           << "\n";

  paramData->Dump();
  cout << "\n";

  // Test Assess option only
  haruspex->SetLearnOption( true );
  haruspex->SetDeriveOption( true );
  haruspex->SetTestOption( false );
  haruspex->SetAssessOption( false );

  haruspex->Update();
  outputMetaDS = vtkMultiBlockDataSet::SafeDownCast(
                 haruspex->GetOutputDataObject( vtkStatisticsAlgorithm::OUTPUT_MODEL ) );
  for ( unsigned int b = 0; b < outputMetaDS->GetNumberOfBlocks(); ++ b )
    {
    vtkTable* outputMeta = vtkTable::SafeDownCast( outputMetaDS->GetBlock( b ) );
    if ( b == 0 )
      {
      vtkIdType r = 0;
      vtkIdType testIntValue = 0;
      for( int curRun = 0; curRun < numRuns; curRun++ )
        {
        testIntValue = 0;
        for( int nInRun = 0; nInRun < numClustersInRun[curRun]; nInRun++ )
          {
          testIntValue += outputMeta->GetValueByName( r, "Cardinality" ).ToInt();
          r++;
          }
        }

      if ( r != outputMeta->GetNumberOfRows() )
        {
        vtkGenericWarningMacro("Inconsistency in number of rows: "
                               << r
                               << " != "
                               << outputMeta->GetNumberOfRows()
                               << ".");
        testStatus = 1;
        }

      cout << "## Computed clusters (cardinality: "
           << testIntValue
           << " / run):\n";

      if ( testIntValue != nVals )
        {
        vtkGenericWarningMacro("Sum of cluster cardinalities is incorrect: "
                               << testIntValue
                               << " != "
                               << nVals
                               << ".");
        testStatus = 1;
        }
      }
    else
      {
      cout << "## Ranked cluster: "
           << "\n";
      }

    outputMeta->Dump();
    cout << "\n";
    }

  cout << "=================== ASSESS ==================== " << endl;
  vtkMultiBlockDataSet* paramsTables = vtkMultiBlockDataSet::New();
  paramsTables->ShallowCopy( outputMetaDS );

  haruspex->SetInput( vtkStatisticsAlgorithm::INPUT_MODEL, paramsTables );

  // Test Assess option only (do not recalculate nor rederive a model)
  haruspex->SetLearnOption( false );
  haruspex->SetDeriveOption( false );
  haruspex->SetTestOption( false );
  haruspex->SetAssessOption( true );
  haruspex->Update();
  vtkTable* outputData = haruspex->GetOutput();
  outputData->Dump();
  paramsTables->Delete();
  paramData->Delete();
  inputData->Delete();
  haruspex->Delete();

  return testStatus;
}
