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

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

#include "vtkObjectFactory.h"
#include "vtkWindowToImageFilter.h"
#include "vtkPNGWriter.h"
#include "vtkImageShiftScale.h"
#include "vtkImageDifference.h"
#include "vtkPNGReader.h"
#include "vtkRenderWindow.h"
#include "vtkImageData.h"
#include "vtkTimerLog.h"
#include "vtkSmartPointer.h"
#include "vtkNew.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkInteractorEventRecorder.h"
#include "vtkImageClip.h"
#include "vtkToolkits.h"
#include "vtkDataSet.h"
#include "vtkPointSet.h"
#include "vtkPointData.h"
#include "vtkDataArray.h"
#include "vtkDoubleArray.h"
#include "vtkFloatArray.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkInformation.h"
#include "vtkAlgorithm.h"

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

#include <sys/stat.h>

#include <vtksys/SystemTools.hxx>

vtkStandardNewMacro(vtkTesting);
vtkCxxSetObjectMacro(vtkTesting, RenderWindow, vtkRenderWindow);

using std::vector;
using std::string;

//-----------------------------------------------------------------------------
// Find in command tail, failing that find in environment,
// failing that return a default.
// Up to caller to delete the string returned.
string vtkTestingGetArgOrEnvOrDefault(
          string argName,       // argument idnetifier flag. eg "-D"
          vector<string> &argv, // command tail
          string env,           // environment variable name to find
          string def)           // default to use if "env" is not found.
{
  string argValue;

  // Serach command tail.
  int argc=static_cast<int>(argv.size());
  for (int i=0; i<argc; i++)
    {
    if (argName==argv[i] && i<(argc-1))
      {
      argValue=argv[i+1];
      }
    }
  // If not found search environment.
  if (argValue.empty()
      && !(env.empty() || def.empty()))
    {
    char *foundenv=getenv(env.c_str());
    if (foundenv)
      {
      argValue=foundenv;
      }
    else
      {
      // Not found, fall back to default.
      argValue=def;
      }
    }

  return argValue;
}


//-----------------------------------------------------------------------------
// Description:
// Sum the L2 Norm point wise over all tuples. Each term
// is scaled by the magnitude of one of the inputs.
// Return sum and the number of terms.
template <class T>
vtkIdType AccumulateScaledL2Norm(
        T *pA,           // pointer to first data array
        T *pB,           // pointer to second data array
        vtkIdType nTups, // number of tuples
        int nComps,      // number of comps
        double &SumModR) // result
{
  //
  SumModR=0.0;
  for (vtkIdType i=0; i<nTups; ++i)
    {
    double modR=0.0;
    double modA=0.0;
    for (int q=0; q<nComps; ++q)
      {
      double a=pA[q];
      double b=pB[q];
      modA+=a*a;
      double r=b-a;
      modR+=r*r;
      }
    modA=sqrt(modA);
    modA= modA<1.0 ? 1.0 : modA;
    SumModR+=sqrt(modR)/modA;
    pA+=nComps;
    pB+=nComps;
    }
  return nTups;
}

//=============================================================================
vtkTesting::vtkTesting()
{
  this->FrontBuffer = 0;
  this->RenderWindow = 0;
  this->ValidImageFileName = 0;
  this->ImageDifference = 0;
  this->DataRoot = 0;
  this->TempDirectory = 0;
  this->BorderOffset = 0;
  this->Verbose = 0;

  // on construction we start the timer
  this->StartCPUTime = vtkTimerLog::GetCPUTime();
  this->StartWallTime = vtkTimerLog::GetUniversalTime();
}
//-----------------------------------------------------------------------------
vtkTesting::~vtkTesting()
{
  this->SetRenderWindow(0);
  this->SetValidImageFileName(0);
  this->SetDataRoot(0);
  this->SetTempDirectory(0);
}
//-----------------------------------------------------------------------------
void vtkTesting::AddArgument(const char *arg)
{
  this->Args.push_back(arg);
}
//-----------------------------------------------------------------------------
void vtkTesting::AddArguments(int argc,const char **argv)
{
  for (int i=0; i<argc; ++i)
    {
    this->Args.push_back(argv[i]);
    }
}
//-----------------------------------------------------------------------------
char *vtkTesting::GetArgument(const char *argName)
{
  string argValue
    = vtkTestingGetArgOrEnvOrDefault(argName,this->Args,"","");

  char *cArgValue=new char [argValue.size()+1];
  strcpy(cArgValue,argValue.c_str());

  return cArgValue;
}
//-----------------------------------------------------------------------------
void vtkTesting::CleanArguments()
{
  this->Args.erase( this->Args.begin(), this->Args.end() );
}
//-----------------------------------------------------------------------------
const char *vtkTesting::GetDataRoot()
{
#ifdef VTK_DATA_ROOT
  string dr=vtkTestingGetArgOrEnvOrDefault(
                "-D",this->Args,"VTK_DATA_ROOT",VTK_DATA_ROOT);
#else
  string dr=vtkTestingGetArgOrEnvOrDefault(
                "-D",this->Args, "VTK_DATA_ROOT","../../../../VTKData");
#endif
  this->SetDataRoot(
     vtksys::SystemTools::CollapseFullPath(dr.c_str()).c_str());

  return this->DataRoot;
}
//-----------------------------------------------------------------------------
const char *vtkTesting::GetTempDirectory()
{
  string td=vtkTestingGetArgOrEnvOrDefault(
                "-T",this->Args, "VTK_TEMP_DIR","../../../Testing/Temporary");
  this->SetTempDirectory(
    vtksys::SystemTools::CollapseFullPath(td.c_str()).c_str());

  return this->TempDirectory;
}
//-----------------------------------------------------------------------------
const char *vtkTesting::GetValidImageFileName()
{
  this->SetValidImageFileName(0);
  if (!this->IsValidImageSpecified())
    {
    return this->ValidImageFileName;
    }

  string baseline=vtkTestingGetArgOrEnvOrDefault(
                "-B", this->Args,"VTK_BASELINE_ROOT", this->GetDataRoot());

  for (size_t i=0; i<(this->Args.size()-1); ++i)
    {
    if ( this->Args[i] == "-V")
      {
      const char *ch = this->Args[i+1].c_str();
      if ( ch[0] == '/'
#ifdef _WIN32
        || (ch[0] >= 'a' && ch[0] <= 'z' && ch[1] == ':' )
        || (ch[0] >= 'A' && ch[0] <= 'Z' && ch[1] == ':' )
#endif
        )
        {
        baseline = this->Args[i+1];
        }
      else
        {
        baseline += "/";
        baseline += this->Args[i+1];
        }
      break;
      }
    }

  this->SetValidImageFileName(baseline.c_str());

  return this->ValidImageFileName;
}
//-----------------------------------------------------------------------------
int vtkTesting::IsInteractiveModeSpecified()
{
  unsigned int i;
  for (i = 0; i < this->Args.size(); ++i)
    {
    if ( this->Args[i] == "-I")
      {
      return 1;
      }
    }
  return 0;
}
//-----------------------------------------------------------------------------
int vtkTesting::IsFlagSpecified(const char *flag)
{
  unsigned int i;
  for (i = 0; i < this->Args.size(); ++i)
    {
    if ( this->Args[i] == flag)
      {
      return 1;
      }
    }
  return 0;
}
//-----------------------------------------------------------------------------
int vtkTesting::IsValidImageSpecified()
{
  unsigned int i;
  for (i = 1; i < this->Args.size(); ++i)
    {
    if ( this->Args[i-1] == "-V")
      {
      return 1;
      }
    }
  return 0;
}
//-----------------------------------------------------------------------------
char* vtkTesting::IncrementFileName(const char* fname, int count)
{
  char counts[256];
  sprintf(counts, "%d", count);

  int orgLen = static_cast<int>(strlen(fname));
  if (orgLen < 5)
    {
    return 0;
    }
  int extLen = static_cast<int>(strlen(counts));
  char* newFileName = new char[orgLen+extLen+2];
  strcpy(newFileName, fname);

  newFileName[orgLen-4] = '_';
  int i, marker;
  for(marker=orgLen-3, i=0; marker < orgLen-3+extLen; marker++, i++)
    {
    newFileName[marker] = counts[i];
    }
  strcpy( newFileName + marker, ".png" );

  return newFileName;
}
//-----------------------------------------------------------------------------
int vtkTesting::LookForFile(const char* newFileName)
{
  if (!newFileName)
    {
    return 0;
    }
  struct stat fs;
  if (stat(newFileName, &fs) != 0)
    {
    return 0;
    }
  else
    {
    return 1;
    }
}
//-----------------------------------------------------------------------------
int vtkTesting::RegressionTest(vtkAlgorithm* imageSource, double thresh)
{
  int result = this->RegressionTest(imageSource, thresh, cout);

  cout << "<DartMeasurement name=\"WallTime\" type=\"numeric/double\">";
  cout << vtkTimerLog::GetUniversalTime() - this->StartWallTime;
  cout << "</DartMeasurement>\n";
  cout << "<DartMeasurement name=\"CPUTime\" type=\"numeric/double\">";
  cout << vtkTimerLog::GetCPUTime() - this->StartCPUTime;
  cout << "</DartMeasurement>\n";

  return result;
}
//-----------------------------------------------------------------------------
int vtkTesting::RegressionTest(double thresh)
{
  int result = this->RegressionTest(thresh, cout);

  cout << "<DartMeasurement name=\"WallTime\" type=\"numeric/double\">";
  cout << vtkTimerLog::GetUniversalTime() - this->StartWallTime;
  cout << "</DartMeasurement>\n";
  cout << "<DartMeasurement name=\"CPUTime\" type=\"numeric/double\">";
  cout << vtkTimerLog::GetCPUTime() - this->StartCPUTime;
  cout << "</DartMeasurement>\n";

  return result;
}
//-----------------------------------------------------------------------------
int vtkTesting::RegressionTest(double thresh, ostream &os)
{
  VTK_CREATE(vtkWindowToImageFilter, rt_w2if);
  rt_w2if->SetInput(this->RenderWindow);

  unsigned int i;
  for (i=0; i<this->Args.size(); i++)
    {
    if ( strcmp("-FrontBuffer", this->Args[i].c_str()) == 0 )
      {
      this->FrontBufferOn();
      }
    else if ( strcmp("-NoRerender", this->Args[i].c_str()) == 0 )
      {
      rt_w2if->ShouldRerenderOff();
      }
    }

  // perform and extra render to make sure it is displayed
  if ( !this->FrontBuffer)
    {
    this->RenderWindow->Render();
    // tell it to read the back buffer
    rt_w2if->ReadFrontBufferOff();
    }
  else
    {
    // read the front buffer
    rt_w2if->ReadFrontBufferOn();
    }

  rt_w2if->Update();
  int res = this->RegressionTest(rt_w2if, thresh, os);
  return res;
}
//-----------------------------------------------------------------------------
int vtkTesting::RegressionTest(const std::string &pngFileName, double thresh)
{
  return this->RegressionTest(pngFileName, thresh, cout);
}
//-----------------------------------------------------------------------------
int vtkTesting::RegressionTest(const std::string &pngFileName, double thresh,
                               ostream &os)
{
  vtkNew<vtkPNGReader> inputReader;
  inputReader->SetFileName(pngFileName.c_str());
  inputReader->Update();
  return this->RegressionTest(inputReader.GetPointer(), thresh, os);
}
//-----------------------------------------------------------------------------
int vtkTesting::RegressionTest(vtkAlgorithm* imageSource,
                               double thresh,
                               ostream& os)
{
  // do a get to compute the real value
  this->GetValidImageFileName();
  std::string tmpDir = this->GetTempDirectory();

  // construct the names for the error images
  std::string validName = this->ValidImageFileName;
  std::string::size_type slash_pos = validName.rfind("/");
  if(slash_pos != std::string::npos)
    {
    validName = validName.substr(slash_pos + 1);
    }

  // check the valid image
  FILE *rt_fin = fopen(this->ValidImageFileName,"r");
  if (rt_fin)
    {
    fclose(rt_fin);
    }
  else // there was no valid image, so write one to the temp dir
    {
    std::string vImage = tmpDir + "/" + validName;
    VTK_CREATE(vtkPNGWriter, rt_pngw);
    rt_pngw->SetFileName(vImage.c_str());
    rt_pngw->SetInputConnection(imageSource->GetOutputPort());
    rt_pngw->Write();
    os << "<DartMeasurement name=\"ImageNotFound\" type=\"text/string\">"
      << this->ValidImageFileName << "</DartMeasurement>" << endl;
    return FAILED;
    }

  VTK_CREATE(vtkPNGReader, rt_png);
  rt_png->SetFileName(this->ValidImageFileName);
  rt_png->Update();
  imageSource->Update();

  VTK_CREATE(vtkImageDifference, rt_id);

  VTK_CREATE(vtkImageClip, ic1);
  ic1->SetClipData(1);
  ic1->SetInputConnection(imageSource->GetOutputPort());

  VTK_CREATE(vtkImageClip, ic2);
  ic2->SetClipData(1);
  ic2->SetInputConnection(rt_png->GetOutputPort());

  int* wExt1 = ic1->GetInputInformation()->Get(
    vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT());
  int* wExt2 = ic2->GetInputInformation()->Get(
    vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT());
  ic1->SetOutputWholeExtent(wExt1[0] + this->BorderOffset,
                            wExt1[1] - this->BorderOffset,
                            wExt1[2] + this->BorderOffset,
                            wExt1[3] - this->BorderOffset,
                            wExt1[4],
                            wExt1[5]);

  ic2->SetOutputWholeExtent(wExt2[0] + this->BorderOffset,
                            wExt2[1] - this->BorderOffset,
                            wExt2[2] + this->BorderOffset,
                            wExt2[3] - this->BorderOffset,
                            wExt2[4],
                            wExt2[5]);

  int ext1[6], ext2[6];
  rt_id->SetInputConnection(ic1->GetOutputPort());
  ic1->Update();
  ic1->GetOutput()->GetExtent(ext1);
  rt_id->SetImageConnection(ic2->GetOutputPort());
  ic2->Update();
  ic2->GetOutput()->GetExtent(ext2);

  double minError = VTK_DOUBLE_MAX;

  if ((ext2[1]-ext2[0]) == (ext1[1]-ext1[0]) &&
      (ext2[3]-ext2[2]) == (ext1[3]-ext1[2]) &&
      (ext2[5]-ext2[4]) == (ext1[5]-ext1[4]))
    {
    // Cannot compute difference unless image sizes are the same
    rt_id->Update();
    minError = rt_id->GetThresholdedError();
    }

  this->ImageDifference = minError;
  int passed = 0;
  if (minError <= thresh)
    {
    // Make sure there was actually a difference image before
    // accepting the error measure.
    vtkImageData* output = rt_id->GetOutput();
    if(output)
      {
      int dims[3];
      output->GetDimensions(dims);
      if(dims[0]*dims[1]*dims[2] > 0)
        {
        passed = 1;
        }
      else
        {
        vtkErrorMacro("ImageDifference produced output with no data.");
        }
      }
    else
      {
      vtkErrorMacro("ImageDifference did not produce output.");
      }
    }

  // If the test failed with the first image (foo.png) check if there are
  // images of the form foo_N.png (where N=1,2,3...) and compare against
  // them.
  double error;
  int count=1, errIndex=-1;
  char* newFileName;
  while (!passed)
    {
    newFileName = IncrementFileName(this->ValidImageFileName, count);
    if (!LookForFile(newFileName))
      {
      delete[] newFileName;
      break;
      }

    rt_png->SetFileName(newFileName);

    // Need to reset the output whole extent cause we may have baselines
    // of differing sizes. (Yes, we have such cases !)
    ic2->ResetOutputWholeExtent();
    ic2->SetOutputWholeExtent(wExt2[0] + this->BorderOffset,
                              wExt2[1] - this->BorderOffset,
                              wExt2[2] + this->BorderOffset,
                              wExt2[3] - this->BorderOffset,
                              wExt2[4],
                              wExt2[5]);
    ic2->UpdateWholeExtent();

    rt_id->GetImage()->GetExtent(ext2);
    if ((ext2[1]-ext2[0]) == (ext1[1]-ext1[0]) &&
        (ext2[3]-ext2[2]) == (ext1[3]-ext1[2]) &&
        (ext2[5]-ext2[4]) == (ext1[5]-ext1[4]))
      {
      // Cannot compute difference unless image sizes are the same
      rt_id->Update();
      error = rt_id->GetThresholdedError();
      }
    else
      {
      error = VTK_DOUBLE_MAX;
      }

    if (error <= thresh)
      {
      // Make sure there was actually a difference image before
      // accepting the error measure.
      vtkImageData* output = rt_id->GetOutput();
      if(output)
        {
        int dims[3];
        output->GetDimensions(dims);
        if(dims[0]*dims[1]*dims[2] > 0)
          {
          minError = error;
          passed = 1;
          }
        }
      }
    else
      {
      if (error < minError)
        {
        errIndex = count;
        minError = error;
        }
      }
    ++count;
    delete[] newFileName;
    }

  // output some information
  os << "<DartMeasurement name=\"ImageError\" type=\"numeric/double\">";
  os << minError;
  os << "</DartMeasurement>";
  if ( errIndex <= 0)
    {
    os << "<DartMeasurement name=\"BaselineImage\" type=\"text/string\">Standard</DartMeasurement>";
    }
  else
    {
    os <<  "<DartMeasurement name=\"BaselineImage\" type=\"numeric/integer\">";
    os << errIndex;
    os << "</DartMeasurement>";
    }

  if (passed)
    {
    return PASSED;
    }

  os << "Failed Image Test : " << minError << endl;
  if (errIndex >= 0)
    {
    newFileName = IncrementFileName(this->ValidImageFileName, errIndex);
    rt_png->SetFileName(newFileName);
    delete[] newFileName;
    }
  else
    {
    rt_png->SetFileName(this->ValidImageFileName);
    }

  rt_png->Update();
  rt_id->GetImage()->GetExtent(ext2);

  // If no image differences produced an image, do not write a
  // difference image.
  if(minError <= 0)
    {
    os << "Image differencing failed to produce an image." << endl;
    return FAILED;
    }
  if(!(
      (ext2[1]-ext2[0]) == (ext1[1]-ext1[0]) &&
      (ext2[3]-ext2[2]) == (ext1[3]-ext1[2]) &&
      (ext2[5]-ext2[4]) == (ext1[5]-ext1[4])))
    {
    os << "Image differencing failed to produce an image because images are "
      "different size:" << endl;
    os << "Valid image: " << (ext2[1]-ext2[0]) << ", " << (ext2[3]-ext2[2])
      << ", " << (ext2[5]-ext2[4]) << endl;
    os << "Test image: " << (ext1[1]-ext1[0]) << ", " << (ext1[3]-ext1[2])
      << ", " << (ext1[5]-ext1[4]) << endl;
    return FAILED;
    }

  rt_id->Update();

  // test the directory for writing
  std::string diff_filename = tmpDir + "/" + validName;
  std::string::size_type dot_pos = diff_filename.rfind(".");
  if(dot_pos != std::string::npos)
    {
    diff_filename = diff_filename.substr(0, dot_pos);
    }
  diff_filename += ".diff.png";
  FILE *rt_dout = fopen(diff_filename.c_str(), "wb");
  if (rt_dout)
    {
    fclose(rt_dout);

    // write out the difference image gamma adjusted for the dashboard
    VTK_CREATE(vtkImageShiftScale, rt_gamma);
    rt_gamma->SetInputConnection(rt_id->GetOutputPort());
    rt_gamma->SetShift(0);
    rt_gamma->SetScale(10);

    VTK_CREATE(vtkPNGWriter, rt_pngw);
    rt_pngw->SetFileName(diff_filename.c_str());
    rt_pngw->SetInputConnection(rt_gamma->GetOutputPort());
    rt_pngw->Write();

    // write out the image that was generated
    std::string vImage = tmpDir + "/" + validName;
    rt_pngw->SetFileName(vImage.c_str());
    rt_pngw->SetInputConnection(imageSource->GetOutputPort());
    rt_pngw->Write();

    os <<  "<DartMeasurementFile name=\"TestImage\" type=\"image/png\">";
    os << vImage;
    os << "</DartMeasurementFile>";
    os << "<DartMeasurementFile name=\"DifferenceImage\" type=\"image/png\">";
    os << diff_filename;
    os << "</DartMeasurementFile>";
    os << "<DartMeasurementFile name=\"ValidImage\" type=\"image/png\">";
    os << this->ValidImageFileName;
    os <<  "</DartMeasurementFile>";
    }

  return FAILED;
}
//-----------------------------------------------------------------------------
int vtkTesting::Test(int argc, char *argv[], vtkRenderWindow *rw,
                     double thresh )
{
  VTK_CREATE(vtkTesting, testing);
  int i;
  for (i = 0; i < argc; ++i)
    {
    testing->AddArgument(argv[i]);
    }

  if (testing->IsInteractiveModeSpecified())
    {
    return DO_INTERACTOR;
    }

  testing->FrontBufferOff();
  for (i=0; i<argc; i++)
    {
    if ( strcmp("-FrontBuffer", argv[i]) == 0 )
      {
      testing->FrontBufferOn();
      }
    }

  if (testing->IsValidImageSpecified())
    {
    testing->SetRenderWindow(rw);
    int res = testing->RegressionTest(thresh);
    return res;
    }

  return NOT_RUN;
}
//-----------------------------------------------------------------------------
int vtkTesting::CompareAverageOfL2Norm(
        vtkDataArray *daA,
        vtkDataArray *daB,
        double tol)
{
  int typeA=daA->GetDataType();
  int typeB=daB->GetDataType();
  if (typeA!=typeB)
    {
    vtkWarningMacro("Incompatible data types: "
                    << typeA << ","
                    << typeB << ".");
    return 0;
    }
  //
  vtkIdType nTupsA=daA->GetNumberOfTuples();
  vtkIdType nTupsB=daB->GetNumberOfTuples();
  int nCompsA=daA->GetNumberOfComponents();
  int nCompsB=daB->GetNumberOfComponents();
  //
  if ((nTupsA!=nTupsB)
     || (nCompsA!=nCompsB))
    {
    vtkWarningMacro(
              "Arrays: " << daA->GetName()
              << " (nC=" << nCompsA
              << " nT= "<< nTupsA << ")"
              << " and " << daB->GetName()
              << " (nC=" << nCompsB
              << " nT= "<< nTupsB << ")"
              << " do not have the same structure.");
    return 0;
    }

  double L2=0.0;
  vtkIdType N=0;
  switch (typeA)
    {
    case VTK_DOUBLE:
      {
      vtkDoubleArray *A=vtkDoubleArray::SafeDownCast(daA);
      double *pA=A->GetPointer(0);
      vtkDoubleArray *B=vtkDoubleArray::SafeDownCast(daB);
      double *pB=B->GetPointer(0);
      N=AccumulateScaledL2Norm(pA,pB,nTupsA,nCompsA,L2);
      }
      break;
    case VTK_FLOAT:
      {
      vtkFloatArray *A=vtkFloatArray::SafeDownCast(daA);
      float *pA=A->GetPointer(0);
      vtkFloatArray *B=vtkFloatArray::SafeDownCast(daB);
      float *pB=B->GetPointer(0);
      N=AccumulateScaledL2Norm(pA,pB,nTupsA,nCompsA,L2);
      }
      break;
    default:
      if (this->Verbose)
        {
        cout << "Skipping:" << daA->GetName() << endl;
        }
      return true;
      break;
    }
  //
  if (N<=0)
  {
    return 0;
  }
  //
  if (this->Verbose)
    {
    cout << "Sum(L2)/N of "
         << daA->GetName()
         << " < " << tol
         << "? = " << L2
         << "/" << N
         << "."  << endl;
    }
  //
  double avgL2=L2/static_cast<double>(N);
  if (avgL2>tol)
    {
    return 0;
    }

  // Test passed
  return 1;
}
//-----------------------------------------------------------------------------
int vtkTesting::CompareAverageOfL2Norm(
        vtkDataSet *dsA,
        vtkDataSet *dsB,
        double tol)
{
  vtkDataArray *daA=0;
  vtkDataArray *daB=0;
  int status=0;

  // Compare points if the dataset derives from
  // vtkPointSet.
  vtkPointSet *ptSetA=vtkPointSet::SafeDownCast(dsA);
  vtkPointSet *ptSetB=vtkPointSet::SafeDownCast(dsB);
  if (ptSetA!=NULL && ptSetB!=NULL)
    {
    if (this->Verbose)
      {
      cout << "Comparing points:" << endl;
      }
    daA=ptSetA->GetPoints()->GetData();
    daB=ptSetB->GetPoints()->GetData();
    //
    status=CompareAverageOfL2Norm(daA,daB,tol);
    if (status==0)
      {
      return 0;
      }
    }

  // Compare point data arrays.
  if (this->Verbose)
    {
    cout << "Comparing data arrays:" << endl;
    }
  int nDaA=dsA->GetPointData()->GetNumberOfArrays();
  int nDaB=dsB->GetPointData()->GetNumberOfArrays();
  if (nDaA!=nDaB)
    {
    vtkWarningMacro("Point data, " << dsA
              <<  " and " << dsB << " differ in number of arrays"
              <<  " and cannot be compared.");
    return 0;
    }
  //
  for (int arrayId=0; arrayId<nDaA; ++arrayId)
    {
    daA=dsA->GetPointData()->GetArray(arrayId);
    daB=dsB->GetPointData()->GetArray(arrayId);
    //
    status=CompareAverageOfL2Norm(daA,daB,tol);
    if (status==0)
      {
      return 0;
      }
    }
  // All tests passed.
  return 1;
}

//-----------------------------------------------------------------------------
int vtkTesting::InteractorEventLoop( int argc,
                                     char *argv[],
                                     vtkRenderWindowInteractor *iren,
                                     const char *playbackStream )
{
  bool disableReplay = false, record = false;
  for (int i = 0; i < argc; i++)
    {
    disableReplay |= (strcmp("--DisableReplay", argv[i]) == 0);
    record        |= (strcmp("--Record", argv[i]) == 0);
    }

  vtkSmartPointer<vtkInteractorEventRecorder> recorder =
      vtkSmartPointer<vtkInteractorEventRecorder>::New();
  recorder->SetInteractor(iren);

  if (!disableReplay)
    {

    if (record)
      {
      recorder->SetFileName("vtkInteractorEventRecorder.log");
      recorder->On();
      recorder->Record();
      }
    else
      {
      if (playbackStream)
        {
        recorder->ReadFromInputStringOn();
        recorder->SetInputString(playbackStream);
        recorder->Play();

        // Without this, the "-I" option if specified will fail
        recorder->Off();
        }
      }
    }

  // iren will be either the object factory instantiation (vtkTestingInteractor)
  // or vtkRenderWindowInteractor depending on whether or not "-I" is specified.
  iren->Start();

  recorder->Off();

  return EXIT_SUCCESS;
}

//-----------------------------------------------------------------------------
void vtkTesting::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  os << indent << "RenderWindow: " << this->RenderWindow << endl;
  os << indent << "ValidImageFileName: " << (this->ValidImageFileName?this->ValidImageFileName:"(none)") << endl;
  os << indent << "FrontBuffer: " << (this->FrontBuffer?"On":"Off") << endl;
  os << indent << "ImageDifference: " << this->ImageDifference << endl;
  os << indent << "DataRoot: " << this->GetDataRoot() << endl;
  os << indent << "Temp Directory: " << this->GetTempDirectory() << endl;
  os << indent << "BorderOffset: " << this->GetBorderOffset() << endl;
  os << indent << "Verbose: " << this->GetVerbose() << endl;
}
