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

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.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 <iostream>

#include "vtksys/CommandLineArguments.hxx"
#include "vtksys/SystemTools.hxx"
#include "vtkMultiThreader.h" 
#include <string>

#ifndef _WIN32
#include <unistd.h>
#else
#include "vtkWindows.h"
#endif

#include "vtkImageData.h"
#include "vtkPNGReader.h"
#include "vtkSmartPointer.h"
#include "vtkVVTesting.h"


class VolViewRunner
{
public:
  
  enum 
    {
    RUNNING,
    FAILED,
    SUCCESS,
    NOT_RUN
    };
  
  VolViewRunner() 
    : Result( VolViewRunner::NOT_RUN ),
      ProcessThreadId( -1 )
    {
    this->MultiThreader = vtkMultiThreader::New();
    this->Args          = "";
    }

  ~VolViewRunner()
    {
    this->MultiThreader->Delete();
    
    }
    

  int GetResult()
    {
    return this->Result;
    }

  const char *GetArgs()
    {
    return this->Args.c_str();
    }
    

  int RunArgsOnSeparateThread( const char * args )
    {
    this->Result = VolViewRunner::NOT_RUN;
    this->Args = args;
    this->ProcessThreadId = this->MultiThreader->SpawnThread((vtkThreadFunctionType)\
                                  &RunCommandThread, this);
    return ProcessThreadId;
    }

  int TerminateCurrentThread()
    {
    if ( this->ProcessThreadId < 0 )
      {
      // No process running
      return 0;
      }
    
    int res = 0;
    this->MultiThreader->TerminateThread( this->ProcessThreadId );
    if ( this->Result == VolViewRunner::SUCCESS )
      {
      res = 1;
      }

    return res;
    }
    
  
protected:
  static void * RunCommandThread( vtkMultiThreader::ThreadInfo *s )
    {
    VolViewRunner * runner = (VolViewRunner *)(s->UserData);
    runner->Result = VolViewRunner::RUNNING;
    system( runner->GetArgs() );  
    runner->Result = VolViewRunner::SUCCESS;
    return NULL;
    }
    

private:
  
  int                Result;
  int                ProcessThreadId;
  vtkMultiThreader * MultiThreader;
  std::string        Args;  
};


int main( int argc, char *argv[] )
{
  typedef vtksys::CommandLineArguments argT;
  argT arg;
  arg.Initialize( argc, argv );
  
  char *script            = 0;  // A .exe playback script
  char *vv3Executable     = 0;  // VolView executable
  char *baselineSnapshot  = 0;  // Baseline PNG to compare with
  char *generatedSnapshot = 0;  // Snapshot saved PNG (will be compared to the baseline)
  char *tmpDir            = 0;  // Where the difference images etc will be written
  char *testDescription   = 0;  // Informative message
  char *loadData          = 0;  // initial data to load up in VV
  
  // The following arguments must be present
  arg.AddArgument("--script",              argT::SPACE_ARGUMENT, &script,        " "); 
  arg.AddArgument("--volview3-executable", argT::SPACE_ARGUMENT, &vv3Executable, " "); 

  // Optional arguments
  arg.AddArgument("--baseline-snapshot",  argT::SPACE_ARGUMENT, &baselineSnapshot,  " "); 
  arg.AddArgument("--generated-snapshot", argT::SPACE_ARGUMENT, &generatedSnapshot, " "); 
  arg.AddArgument("--temp-directory",     argT::SPACE_ARGUMENT, &tmpDir,            " "); 

  arg.AddArgument("--load-data",          argT::SPACE_ARGUMENT, &loadData,          " ");
  arg.AddArgument("--test-description",   argT::SPACE_ARGUMENT, &testDescription,   " "); 
  
  arg.Parse();

  // If the generated snapshot already exists make sure it is removed
  if (generatedSnapshot)
    {
    vtksys::SystemTools::RemoveFile( generatedSnapshot );
    }
  
  // Run VolView3 on a separate thread

  std::string vv3Args = 
    vtksys::SystemTools::ConvertToOutputPath(vv3Executable);
#if defined(_WIN32) && defined(CMAKE_INTDIR)
  vtksys::SystemTools::ReplaceString(vv3Args, "$(OutDir)", CMAKE_INTDIR);
#endif

  if (!vtksys::SystemTools::FileExists(vv3Args.c_str()))
    {
    std::cout << "[FAILED] Can not find: " << vv3Args << std::endl;
    return EXIT_FAILURE;
    }

  vv3Args += " -L 0 --g=1024x768+0+0 "; // Default geometry and no registry
  if (loadData)
    {
    vv3Args += loadData; 
    }
    std::cout << vv3Args << std::endl;
        
  VolViewRunner *runner = new VolViewRunner();
  runner->RunArgsOnSeparateThread( vv3Args.c_str() );

  // We anticipate that VV will take some time to start.. so wait for it to 
  // fire up and then run the mouse playback software.
  // Wait for 43 seconds
#ifdef _WIN32    
    Sleep(55000); 
#else
    sleep(8000);
#endif
    
  // Run the mouse recorder
  system( vtksys::SystemTools::ConvertToOutputPath( script ).c_str() );
  
  // Wait for stuff to finish
  while (runner->GetResult() != VolViewRunner::SUCCESS)
    {
#ifdef _WIN32    
    Sleep(1000);
#else
    sleep(1);
#endif
    }

  runner->TerminateCurrentThread();
  
  delete runner;

  // Print informative messages if any
  if (testDescription)
    {
    std::cout << testDescription << std::endl; 
    }

  int res = 0;

  // Compare baseline snapshot and created snapshot if specified
  if (baselineSnapshot && generatedSnapshot)
    {
    vtkSmartPointer<vtkPNGReader> reader = vtkSmartPointer<vtkPNGReader>::New();
    reader->SetFileName( generatedSnapshot ); 
    reader->Update();
    
    vtkVVTesting * tester = vtkVVTesting::New();
    tester->SetImageData( reader->GetOutput() );

    std::string testerArguments = "-T";
    tester->AddArgument( testerArguments.c_str() );
    testerArguments = tmpDir;
    tester->AddArgument( testerArguments.c_str() );
    testerArguments = "-V";
    tester->AddArgument( testerArguments.c_str() );
    testerArguments = baselineSnapshot; 
    tester->AddArgument( testerArguments.c_str() );
    res = tester->RegressionTest( 80 ); // Arbitrary threshold for the difference image
    tester->Delete();
    }
  
  
  if (res == 0)
    {
    std::cout << "[PASSED]" << std::endl;
    return EXIT_SUCCESS;
    }

  std::cout << "[FAILED]" << std::endl;
  return EXIT_FAILURE;
}

