// TestRunnerDlg.cpp : implementation file
//

#include "stdafx.h"
#include "mmsystem.h"
#include "TestRunnerApp.h"
#include "TestRunnerDlg.h"
#include "Resource.h"
#include "ActiveTest.h"
#include "ProgressBar.h"
#include "TreeHierarchyDlg.h"
#include "ListCtrlFormatter.h"
#include "ListCtrlSetter.h"
#include "MfcSynchronizationObject.h"
#include "ResourceLoaders.h"
#include <cppunit/TestFailure.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/* Notes:
 - code duplication between OnOK() and OnQuitApplication()
 - the threading need to be rewrite, so that GUI update occures in the original
 thread, not in the thread that is running the tests. This slow down the time
 needed to run the test much...
 */


/////////////////////////////////////////////////////////////////////////////
// TestRunnerDlg dialog

const CString TestRunnerDlg::ms_cppunitKey( "CppUnit" );


TestRunnerDlg::TestRunnerDlg( TestRunnerModel *model,
                              int nDialogResourceId,
                              CWnd* pParent )
    : cdxCDynamicDialog( nDialogResourceId, pParent )
{
  ASSERT(0); // this constructor should not be used because of possible resource problems
             // => use the constructor with the string parameter
  init(model);
}

TestRunnerDlg::TestRunnerDlg( TestRunnerModel *model,
                              const TCHAR* szDialogResourceId,
                              CWnd* pParent )
    : cdxCDynamicDialog( szDialogResourceId == NULL ? 
                                _T("CPP_UNIT_TEST_RUNNER_IDD_DIALOG_TESTRUNNER")
                              : szDialogResourceId, 
                         pParent)
{
  init(model);
}

void
TestRunnerDlg::init(TestRunnerModel *model)
{
  m_model = model;

  //{{AFX_DATA_INIT(TestRunnerDlg)
    m_bAutorunAtStartup = FALSE;
  //}}AFX_DATA_INIT

  m_testsProgress     = 0;
  m_selectedTest      = 0;
  m_bAutorunAtStartup = true;
  m_bIsRunning = false;
  m_activeTest = 0;
  m_result = 0;
  m_testObserver = 0;

  ModifyFlags( flSWPCopyBits, 0 );      // anti-flickering option for resizing
}

void 
TestRunnerDlg::DoDataExchange(CDataExchange* pDX)
{
  cdxCDynamicDialog::DoDataExchange(pDX);
  //{{AFX_DATA_MAP(TestRunnerDlg)
    DDX_Control(pDX, IDC_DETAILS, m_details);
    DDX_Control(pDX, IDC_LIST, m_listCtrl);
    DDX_Control(pDX, IDOK, m_buttonClose);
    DDX_Control(pDX, ID_STOP, m_buttonStop);
    DDX_Control(pDX, ID_RUN, m_buttonRun);
    DDX_Control(pDX, IDC_BROWSE_TEST, m_buttonBrowse);
    DDX_Check(pDX, IDC_CHECK_AUTORUN, m_bAutorunAtStartup);
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(TestRunnerDlg, cdxCDynamicDialog)
  //{{AFX_MSG_MAP(TestRunnerDlg)
  ON_BN_CLICKED(ID_RUN, OnRun)
  ON_BN_CLICKED(ID_STOP, OnStop)
  ON_BN_CLICKED(IDC_BROWSE_TEST, OnBrowseTest)
  ON_COMMAND(ID_QUIT_APPLICATION, OnQuitApplication)
  ON_WM_CLOSE()
	ON_WM_SIZE()
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST, OnSelectedFailureChange)
	ON_CBN_SELCHANGE(IDC_COMBO_TEST, OnSelectTestInHistoryCombo)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// TestRunnerDlg message handlers

BOOL 
TestRunnerDlg::OnInitDialog() 
{
  cdxCDynamicDialog::OnInitDialog();

#ifdef CPPUNIT_SUBCLASSING_TESTRUNNERDLG_BUILD
  m_hAccelerator = ::LoadAccelerators( AfxGetResourceHandle(),
#else
  m_hAccelerator = ::LoadAccelerators( g_testRunnerResource,
#endif
                                       MAKEINTRESOURCE( IDR_ACCELERATOR_TEST_RUNNER ) );
// It always fails!!! I don't understand why. Complain about not finding the resource name!
  ASSERT( m_hAccelerator !=NULL );
  
  CComboBox   *comboBox = (CComboBox *)GetDlgItem (IDC_COMBO_TEST);

  ASSERT (comboBox);

  VERIFY( m_errorListBitmap.Create( _T("CPP_UNIT_TEST_RUNNER_IDB_ERROR_TYPE"), 
                                    16, 1, 
                                    RGB( 255,0,255 ) ) );

  m_testsProgress = new ProgressBar();
  m_testsProgress->Create( NULL, NULL, WS_CHILD, CRect(), this, 0 );
  m_testsProgress->ShowWindow( SW_SHOW );
  m_testsProgress->MoveWindow( getItemClientRect( IDC_STATIC_PROGRESS_BAR ) );

  initializeLayout();
  loadSettings();
  initializeFixedSizeFont();
  m_details.SetFont( &m_fixedSizeFont );  // Does not work. Need to investigate...
      
  m_listCtrl.SetImageList( &m_errorListBitmap, LVSIL_SMALL );
  m_listCtrl.SetExtendedStyle( m_listCtrl.GetExtendedStyle() | LVS_EX_FULLROWSELECT );

  int total_col_1_4 = m_settings.col_1 + m_settings.col_2 + 
		      m_settings.col_3 + m_settings.col_4;

  CRect listBounds;
  m_listCtrl.GetClientRect(&listBounds);
  int col_5_width = listBounds.Width() - total_col_1_4; // 5th column = rest of listview space
  ListCtrlFormatter formatter( m_listCtrl );
  formatter.AddColumn( loadCString(IDS_ERRORLIST_TYPE), m_settings.col_1, LVCFMT_LEFT, 0 );
  formatter.AddColumn( loadCString(IDS_ERRORLIST_NAME), m_settings.col_2, LVCFMT_LEFT, 1 );
  formatter.AddColumn( loadCString(IDS_ERRORLIST_FAILED_CONDITION), m_settings.col_3, LVCFMT_LEFT, 2 );
  m_listCtrl.setLineNumberSubItem( formatter.GetNextColumnIndex() );
  formatter.AddColumn( loadCString(IDS_ERRORLIST_LINE_NUMBER), m_settings.col_4, LVCFMT_LEFT, 3 );
  m_listCtrl.setFileNameSubItem( formatter.GetNextColumnIndex() );
  formatter.AddColumn( loadCString(IDS_ERRORLIST_FILE_NAME), col_5_width, LVCFMT_LEFT, 4 );

  reset ();
  updateHistoryCombo();
  UpdateData( FALSE );

  updateListColumnSize();

  m_buttonRun.SetFocus();

  if ( m_bAutorunAtStartup )
    OnRun();
  
  return FALSE;  // return TRUE unless you set the focus to a control
                 // EXCEPTION: OCX Property Pages should return FALSE
}


TestRunnerDlg::~TestRunnerDlg()
{ 
  freeState();
  delete m_testsProgress;
}


void 
TestRunnerDlg::OnRun() 
{
  if ( m_bIsRunning )
    return;

  m_selectedTest = m_model->selectedTest();

  if ( m_selectedTest == 0 )
    return;

  freeState(); 
  reset();

  beRunning();

  int numberOfTests = m_selectedTest->countTestCases();

  m_testsProgress->start( numberOfTests );

  
  m_result = new CPPUNIT_NS::TestResultCollector( new MfcSynchronizationObject() );
  m_testObserver = new CPPUNIT_NS::TestResult( new MfcSynchronizationObject() );
  m_testObserver->addListener( m_result );
  m_testObserver->addListener( this );
  m_activeTest = new ActiveTest( m_selectedTest );

  m_testStartTime = timeGetTime();

  m_activeTest->run( m_testObserver );

  m_testEndTime = timeGetTime();
}


void 
TestRunnerDlg::addListEntry( const CPPUNIT_NS::TestFailure &failure )
{
  CListCtrl *listCtrl = (CListCtrl *)GetDlgItem (IDC_LIST);
  int currentEntry = m_result->testErrors() + 
                     m_result->testFailures() -1;

  ErrorTypeBitmaps errorType;
  if ( failure.isError() )
    errorType = errorTypeError;
  else
    errorType = errorTypeFailure;

  ListCtrlSetter setter( *listCtrl );
  setter.insertLine( currentEntry );
  setter.addSubItem( failure.isError() ? _T("Error") : _T("Failure"), errorType );

  // Set test name
  setter.addSubItem( failure.failedTestName().c_str(), errorType );

  // Set the asserted text
  CString message( failure.thrownException()->message().shortDescription().c_str() );
  message.Replace( '\n', ' ' );   // should only print the short description there,
  setter.addSubItem( message );   // and dump the detail on an edit control when clicked.

  // Set the line number
  if ( failure.sourceLine().isValid() )
  {
    CString lineNumber;
    lineNumber.Format( _T("%ld"), failure.sourceLine().lineNumber() );
    setter.addSubItem( lineNumber );
  }
  else
    setter.addSubItem( _T("") );

  // Set the file name
  setter.addSubItem( failure.sourceLine().fileName().c_str() );

  if ( !listCtrl->GetFirstSelectedItemPosition() )
  {
    // Select first entry => display details of first entry.
    listCtrl->SetItemState( currentEntry, LVIS_SELECTED, LVIS_SELECTED );
    listCtrl->SetFocus();   // Does not work ?!?
  }

  listCtrl->RedrawItems( currentEntry, currentEntry );
  listCtrl->UpdateWindow();
}


void 
TestRunnerDlg::startTest( CPPUNIT_NS::Test *test )
{
  CWnd *runningTestCaseLabel = GetDlgItem(IDC_RUNNING_TEST_CASE_LABEL);
  if ( runningTestCaseLabel )
    runningTestCaseLabel->SetWindowText( CString( test->getName().c_str() ) );
}


void 
TestRunnerDlg::addFailure( const CPPUNIT_NS::TestFailure &failure )
{
  addListEntry( failure );
  if ( failure.isError() )
    m_errors++;
  else
    m_failures++;

  updateCountsDisplay();
}


void 
TestRunnerDlg::endTest( CPPUNIT_NS::Test *test )
{
  if ( m_selectedTest == 0 )
    return;

  m_testsRun++;
  updateCountsDisplay();
  m_testsProgress->step( m_failures == 0  &&  m_errors == 0 );

  m_testEndTime = timeGetTime();

  updateCountsDisplay();

  if ( m_testsRun >= m_selectedTest->countTestCases() )
    beIdle ();
}


void 
TestRunnerDlg::beRunning()
{
  m_bIsRunning = true;
  m_buttonRun.EnableWindow( FALSE );
  m_buttonClose.EnableWindow( FALSE );
  m_buttonBrowse.EnableWindow( FALSE );

//    m_buttonRun.SetButtonStyle( m_buttonRun.GetButtonStyle() & ~BS_DEFPUSHBUTTON );
//    m_buttonStop.SetButtonStyle( m_buttonStop.GetButtonStyle() | BS_DEFPUSHBUTTON );
}


void 
TestRunnerDlg::beIdle()
{
  m_bIsRunning = false;
  m_buttonRun.EnableWindow( TRUE );
  m_buttonBrowse.EnableWindow( TRUE );
  m_buttonClose.EnableWindow( TRUE );

  m_buttonRun.SetButtonStyle( m_buttonRun.GetButtonStyle() | BS_DEFPUSHBUTTON );
//    m_buttonStop.SetButtonStyle( m_buttonStop.GetButtonStyle() & ~BS_DEFPUSHBUTTON );
}


void 
TestRunnerDlg::beRunDisabled()
{
  m_bIsRunning = false;
  m_buttonRun.EnableWindow( FALSE );
  m_buttonBrowse.EnableWindow( FALSE );
  m_buttonStop.EnableWindow( FALSE );
  m_buttonClose.EnableWindow( TRUE );

//    m_buttonRun.SetButtonStyle( m_buttonRun.GetButtonStyle() | BS_DEFPUSHBUTTON );
//    m_buttonStop.SetButtonStyle( m_buttonStop.GetButtonStyle() & ~BS_DEFPUSHBUTTON );
}


void 
TestRunnerDlg::freeState()
{
  delete m_activeTest;
  delete m_result;
  delete m_testObserver;
  m_activeTest = 0;
  m_result = 0;
  m_testObserver = 0;
}


void 
TestRunnerDlg::reset()
{
  m_testsRun = 0;
  m_errors = 0;
  m_failures = 0;
  m_testEndTime = m_testStartTime;

  updateCountsDisplay();

  freeState();
  CListCtrl *listCtrl = (CListCtrl *)GetDlgItem (IDC_LIST);

  listCtrl->DeleteAllItems();
  m_testsProgress->reset();
  displayFailureDetailsFor( -1 );
}


void 
TestRunnerDlg::updateCountsDisplay()
{
  CStatic *statTestsRun = (CStatic *)GetDlgItem( IDC_STATIC_RUNS );
  CStatic *statErrors = (CStatic *)GetDlgItem( IDC_STATIC_ERRORS );
  CStatic *statFailures = (CStatic *)GetDlgItem( IDC_STATIC_FAILURES );
  CEdit *editTime = (CEdit *)GetDlgItem( IDC_EDIT_TIME );

  CString argumentString;

  argumentString.Format( _T("%d"), m_testsRun );
  statTestsRun->SetWindowText (argumentString);

  argumentString.Format( _T("%d"), m_errors );
  statErrors->SetWindowText( argumentString );

  argumentString.Format( _T("%d"), m_failures );
  statFailures->SetWindowText( argumentString );

  argumentString.Format( _T("Execution time: %3.3lf seconds"), 
                         (m_testEndTime - m_testStartTime) / 1000.0 );
  editTime->SetWindowText( argumentString );
}


void 
TestRunnerDlg::OnStop() 
{
  if ( m_testObserver )
    m_testObserver->stop ();

  beIdle ();
}


void 
TestRunnerDlg::OnOK() 
{
  if ( m_testObserver )
    m_testObserver->stop ();

  UpdateData();
  saveSettings();

  cdxCDynamicDialog::OnOK ();
}


void 
TestRunnerDlg::OnSelectTestInHistoryCombo() 
{
  unsigned int currentSelection = getHistoryCombo()->GetCurSel ();

  if ( currentSelection >= 0  &&
       currentSelection < m_model->history().size() )
  {
    CPPUNIT_NS::Test *selectedTest = m_model->history()[currentSelection];
    m_model->selectHistoryTest( selectedTest );
    updateHistoryCombo();
    beIdle();
  }
  else
    beRunDisabled();
}


void
TestRunnerDlg::updateHistoryCombo()
{
  getHistoryCombo()->LockWindowUpdate();

  getHistoryCombo()->ResetContent();

  const TestRunnerModel::History &history = m_model->history();
  for ( TestRunnerModel::History::const_iterator it = history.begin(); 
        it != history.end(); 
        ++it )
  {
    CPPUNIT_NS::Test *test = *it;
    getHistoryCombo()->AddString( CString(test->getName().c_str()) );
  }

  if ( history.size() > 0 )
  {
    getHistoryCombo()->SetCurSel( 0 );
    beIdle();
  }
  else
  {
    beRunDisabled();
    m_buttonBrowse.EnableWindow( TRUE );
	}

  getHistoryCombo()->UnlockWindowUpdate();
}


void 
TestRunnerDlg::OnBrowseTest() 
{
  TreeHierarchyDlg dlg;
  dlg.setRootTest( m_model->rootTest() );
  if ( dlg.DoModal() == IDOK )
  {
    m_model->selectHistoryTest( dlg.getSelectedTest() );
    updateHistoryCombo();
  }
}


BOOL 
TestRunnerDlg::PreTranslateMessage(MSG* pMsg) 
{
  if ( ::TranslateAccelerator( m_hWnd,
                               m_hAccelerator,
                               pMsg ) )
  {
    return TRUE;
  }
  return cdxCDynamicDialog::PreTranslateMessage(pMsg);
}


CComboBox *
TestRunnerDlg::getHistoryCombo()
{
  CComboBox   *comboBox = (CComboBox *)GetDlgItem (IDC_COMBO_TEST);
  ASSERT (comboBox);
  return comboBox;
}


void
TestRunnerDlg::loadSettings()
{
  m_model->loadSettings(m_settings);
  RestoreWindowPosition( TestRunnerModel::settingKey, 
                         TestRunnerModel::settingMainDialogKey );


  m_bAutorunAtStartup = m_settings.autorunOnLaunch;
}


void
TestRunnerDlg::saveSettings()
{
  m_settings.autorunOnLaunch = ( m_bAutorunAtStartup != 0 );
  StoreWindowPosition( TestRunnerModel::settingKey, 
                       TestRunnerModel::settingMainDialogKey );
  
  m_settings.col_1 = m_listCtrl.GetColumnWidth(0);
  m_settings.col_2 = m_listCtrl.GetColumnWidth(1);
  m_settings.col_3 = m_listCtrl.GetColumnWidth(2);
  m_settings.col_4 = m_listCtrl.GetColumnWidth(3);

  m_model->saveSettings(m_settings);
}


void 
TestRunnerDlg::OnQuitApplication() 
{
  if ( m_testObserver )
    m_testObserver->stop();

  UpdateData();
  saveSettings();
  
  CWinApp *app = AfxGetApp();
  ASSERT( app != NULL );
  app->PostThreadMessage( WM_QUIT, 0, 0 );
}


TestRunnerModel &
TestRunnerDlg::model()
{
  ASSERT( m_model != NULL );
  return *m_model;
}


void 
TestRunnerDlg::OnClose() 
{
	OnOK();
}


CRect 
TestRunnerDlg::getItemWindowRect( unsigned int itemId )
{
  CWnd * pItem = GetDlgItem( itemId );
  CRect rect;
  if ( pItem )
    pItem->GetWindowRect( &rect );
  return rect;
}


CRect 
TestRunnerDlg::getItemClientRect( unsigned int itemId )
{
  CRect rect = getItemWindowRect( itemId );
  if ( !rect.IsRectNull() )
  {
    CPoint clientTopLeft = rect.TopLeft();
    ScreenToClient( &clientTopLeft );
    rect = CRect( clientTopLeft, rect.Size() );
  }

  return rect;
}


void 
TestRunnerDlg::initializeLayout()
{
  // see DynamicWindow/doc for documentation
  const int listGrowthRatio = 30;
  AddSzXControl( IDC_COMBO_TEST, mdResize );
  AddSzXControl( IDC_BROWSE_TEST, mdRepos );
  AddSzXControl( IDC_RUNNING_TEST_CASE_LABEL, mdResize );
  AddSzXControl( ID_RUN, mdRepos );
  AddSzXControl( *m_testsProgress, mdResize );
  AddSzXControl( IDC_CHECK_AUTORUN, mdRepos );
  AddSzControl( IDC_LIST, 0, 0, 100, listGrowthRatio );
  AddSzXControl( ID_STOP, mdRepos );
  AddSzXControl( IDOK, mdRepos );
  AddSzYControl( IDC_STATIC_DETAILS, listGrowthRatio, listGrowthRatio );
  AddSzControl( IDC_DETAILS, 0, listGrowthRatio, 100, 100 );
  AddSzControl( IDC_EDIT_TIME, mdResize, mdRepos );
}


void 
TestRunnerDlg::OnSize( UINT nType, int cx, int cy ) 
{
	cdxCDynamicDialog::OnSize(nType, cx, cy);
	updateListColumnSize();
}


void 
TestRunnerDlg::updateListColumnSize()
{
  if ( !m_listCtrl.GetSafeHwnd() )
    return;

  // resize to fit last column
  CRect listBounds = getItemClientRect( IDC_LIST );
  
  int width_1_4 = 0;
  for (int i = 0; i < 4; ++i)
    width_1_4 += m_listCtrl.GetColumnWidth( i );
  
  // the 4 offset is so no horiz scroll bar will appear
  m_listCtrl.SetColumnWidth(4, listBounds.Width() - width_1_4 - 4); 
}


void 
TestRunnerDlg::OnSelectedFailureChange( NMHDR* pNMHDR, 
                                        LRESULT* pResult )
{
	NM_LISTVIEW *pNMListView = (NM_LISTVIEW*)pNMHDR;

  if ( (pNMListView->uNewState & LVIS_SELECTED) != 0 )  // item selected
    displayFailureDetailsFor( pNMListView->iItem );
	
	*pResult = 0;
}


void 
TestRunnerDlg::displayFailureDetailsFor( unsigned int failureIndex )
{
  CString details;
  if ( m_result  &&  failureIndex < m_result->failures().size() )
    details = m_result->failures()[ failureIndex ]->thrownException()->what();

  details.Replace( _T("\n"), _T("\r\n") );

  m_details.SetWindowText( details );
}


void 
TestRunnerDlg::initializeFixedSizeFont()
{
  LOGFONT font;
  GetFont()->GetLogFont( &font );
  font.lfPitchAndFamily = FIXED_PITCH | //VARIABLE_PITCH
                          (font.lfPitchAndFamily & ~15);   // font family
  m_fixedSizeFont.CreateFontIndirect( &font );
}


syntax highlighted by Code2HTML, v. 0.9.1