#include "stdinc.h"
#include "st.h"
#include "stressharness.h"

#define STRESSJOB_INI_SECTION_NAME      (L"[StressJob]")
#define STRESSJOB_INI_KEY_SLEEPTIME     (L"SleepBetweenRuns")
#define STRESSJOB_DEFAULT_SLEEP_TIME    (50)

CStressJobEntry::CStressJobEntry( CStressJobManager *pManager )
{
    this->m_dwSleepBetweenRuns = STRESSJOB_DEFAULT_SLEEP_TIME;
    this->m_fStop = false;
    this->m_pManager = pManager;
    this->m_ulFailures = this->m_ulRuns = 0;    
}

BOOL
CStressJobEntry::LoadFromSettingsFile(
    PCWSTR pcwszFileName
    )
{
    FN_PROLOG_WIN32

    //
    // Right now, we only have one setting, in the [StressJob] section, the
    // time to sleep between runs.  If it's not present, it defaults to
    // 50ms.
    //
    INT ulSleepTime = 0;
    
    IFW32FALSE_EXIT(SxspGetPrivateProfileIntW(
        STRESSJOB_INI_SECTION_NAME,
        STRESSJOB_INI_KEY_SLEEPTIME,
        STRESSJOB_DEFAULT_SLEEP_TIME,
        ulSleepTime,
        pcwszFileName));
    this->m_dwSleepBetweenRuns = static_cast<DWORD>(ulSleepTime);
    
    FN_EPILOG
    
}


//
// DNGN
//
CStressJobEntry::~CStressJobEntry()
{
    FN_TRACE();
    ASSERT(this->m_hThread == CThread::GetInvalidValue());    
}


BOOL
CStressJobEntry::Stop( BOOL fWaitForCompletion )
{
    FN_PROLOG_WIN32
    this->m_fStop = true;
    if ( fWaitForCompletion )
        IFW32FALSE_EXIT(this->WaitForCompletion());
    FN_EPILOG
}



BOOL
CStressJobEntry::WaitForCompletion()
{
    FN_PROLOG_WIN32
    if ( m_hThread != m_hThread.GetInvalidValue() )
    {
        IFW32FALSE_EXIT(WaitForSingleObject(m_hThread, INFINITE) == WAIT_OBJECT_0);
    }
    FN_EPILOG
}



BOOL
CStressJobEntry::Cleanup()
{
    FN_PROLOG_WIN32
    if ( m_hThread != m_hThread.GetInvalidValue() )
    {
        m_hThread.Win32Close();
        m_hThread = m_hThread.GetInvalidValue();
    }
    FN_EPILOG
}



BOOL
CStressJobEntry::WaitForStartingGun()
{
    FN_PROLOG_WIN32
    ASSERT( m_pManager != NULL );
    IFW32FALSE_EXIT(m_pManager->SignalAnotherJobReady());
    IFW32FALSE_EXIT(m_pManager->WaitForStartEvent());
    IFW32FALSE_EXIT(m_pManager->SignalThreadWorking());
    FN_EPILOG
}





DWORD
CStressJobEntry::ThreadProc( PVOID pv )
{
    FN_PROLOG_WIN32
    CStressJobEntry *pEntry = reinterpret_cast<CStressJobEntry*>(pv);
    PARAMETER_CHECK(pEntry != NULL);
    return pEntry->InternalThreadProc();
    FN_EPILOG
}





DWORD 
CStressJobEntry::InternalThreadProc()
{
    if (!this->WaitForStartingGun())
    {
        const DWORD dwError = ::FusionpGetLastWin32Error();
        ::ReportFailure("%ls:%ls failed waiting on starting event, error %ld",
            static_cast<PCWSTR>(m_pManager->GetGroupName()),
            static_cast<PCWSTR>(this->m_buffTestName),
            dwError);
        return 0;
    }

    if (!this->SetupSelfForRun())
    {
        const DWORD dwError = ::FusionpGetLastWin32Error();
        ReportFailure("%ls: test %ls failed to set itself up, error %ld",
            static_cast<PCWSTR>(m_pManager->GetGroupName()),
            static_cast<PCWSTR>(this->m_buffTestName),
            dwError);
    }
    else
    {

        while ( !m_fStop )
        {
            bool fResult;
            if ( RunTest( fResult ) )
            {
                const DWORD dwError = ::FusionpGetLastWin32Error();
                wprintf(L"%ls: test %ls %ls, error %ld\n",
                    static_cast<PCWSTR>(m_pManager->GetGroupName()),
                    static_cast<PCWSTR>(this->m_buffTestName),
                    fResult ? L"passes" : L"fails",
                    dwError);
            }
            else
            {
                const DWORD dwError = ::FusionpGetLastWin32Error();
                ReportFailure("%ls: test %ls failed to complete? Error %ld",
                    static_cast<PCWSTR>(m_pManager->GetGroupName()),
                    static_cast<PCWSTR>(this->m_buffTestName),
                    dwError);
            }

            ::Sleep(this->m_dwSleepBetweenRuns);
            
        }
        
    }
    m_pManager->SignalThreadDone();

    return 1;
    
}



BOOL
CStressJobManager::SignalThreadWorking()
{
    InterlockedIncrement((PLONG)&m_ulThreadsWorking);
    return TRUE;
}



BOOL
CStressJobManager::SignalThreadDone()
{
    InterlockedDecrement((PLONG)&m_ulThreadsWorking);
    return TRUE;
}



CStressJobManager::CStressJobManager()
{
    if (!this->m_hStartingGunEvent.Win32CreateEvent(TRUE, FALSE))
    {
        DebugBreak();
    }

    m_ulThreadsCreated = 0;
    m_ulThreadsReady = 0;
    m_ulThreadsWorking = 0;
}


CStressJobManager::~CStressJobManager()
{
}



BOOL
CStressJobManager::StartJobs()
{
    FN_PROLOG_WIN32
    
    while ( m_ulThreadsReady != m_ulThreadsCreated )
        ::Sleep(10);

    ASSERT(m_hStartingGunEvent != CEvent::GetInvalidValue());
    IFW32FALSE_EXIT(SetEvent(m_hStartingGunEvent));

    FN_EPILOG
}



BOOL
CStressJobManager::CleanupJobs()
{
    FN_PROLOG_WIN32
    CStressEntryDequeIterator Iter(&this->m_JobsListed);
    
    for (Iter.Reset(); Iter.More(); Iter.Next())
    {
        CStressJobEntry *pItem = Iter.Current();
        pItem->Cleanup();
    }
    m_JobsListed.ClearAndDeleteAll();
    FN_EPILOG
}



BOOL
CStressJobManager::StopJobs( 
    BOOL fWithWaitForComplete
    )
{
    FN_PROLOG_WIN32
    CStressEntryDequeIterator Iter(&this->m_JobsListed);
    for ( Iter.Reset(); Iter.More(); Iter.Next() )
    {
        CStressJobEntry *pItem = Iter.Current();
        pItem->Stop(fWithWaitForComplete);
    }

    FN_EPILOG
}



BOOL
CStressJobManager::WaitForAllJobsComplete()
{
    FN_PROLOG_WIN32
    CStressEntryDequeIterator Iter(&this->m_JobsListed);
    for (Iter.Reset(); Iter.More(); Iter.Next())
    {
        CStressJobEntry *pItem = Iter.Current();
        pItem->WaitForCompletion();
    }
    FN_EPILOG
}



BOOL CStressJobManager::CreateWorkerThreads( PULONG pulThreadsCreated )
{
    FN_PROLOG_WIN32

    CStressEntryDequeIterator Iter(&this->m_JobsListed);

    INTERNAL_ERROR_CHECK( m_ulThreadsCreated == 0 );

    if ( pulThreadsCreated ) *pulThreadsCreated = 0;
    m_ulThreadsCreated = 0;

    for ( Iter.Reset(); Iter.More(); Iter.Next() )
    {
        CStressJobEntry *pType = Iter.Current();
        IFW32FALSE_EXIT(pType->m_hThread.Win32CreateThread( pType->ThreadProc, pType ));
        this->m_ulThreadsCreated++;
    }

    if ( pulThreadsCreated ) *pulThreadsCreated = m_ulThreadsCreated;

    FN_EPILOG
}


BOOL CStressJobManager::SignalAnotherJobReady()
{
    ::InterlockedIncrement((PLONG)&m_ulThreadsReady);
    return TRUE;
}



BOOL CStressJobManager::WaitForStartEvent()
{
    return WaitForSingleObject( this->m_hStartingGunEvent, INFINITE ) == WAIT_OBJECT_0;
}


BOOL CStressJobManager::LoadFromDirectory(
    PCWSTR pcwszDirectoryName, 
    PULONG pulJobsFound
    )
{
    FN_PROLOG_WIN32

    CStringBuffer buffSearchString;
    CFindFile Finder;
    WIN32_FIND_DATAW FindData;

    PARAMETER_CHECK(pcwszDirectoryName);
    if ( pulJobsFound ) *pulJobsFound = 0;

    IFW32FALSE_EXIT(buffSearchString.Win32Assign(pcwszDirectoryName, ::wcslen(pcwszDirectoryName)));
    IFW32FALSE_EXIT(buffSearchString.Win32AppendPathElement(L"*", 1));

    Finder = ::FindFirstFileW(buffSearchString, &FindData);
    if (Finder == INVALID_HANDLE_VALUE)
    {
        ::ReportFailure("No tests found in directory %ls", pcwszDirectoryName);
        FN_SUCCESSFUL_EXIT();
    }

    buffSearchString.RemoveLastPathElement();

    do
    {
        CStringBuffer buffFoundName;
        CStressJobEntry *pNextEntry = NULL;

        IFW32FALSE_EXIT(buffFoundName.Win32Assign(buffSearchString));
        IFW32FALSE_EXIT(buffFoundName.Win32AppendPathElement(
            FindData.cFileName,
            ::wcslen(FindData.cFileName)));

        //
        // Let's get ourselves another job entry
        //
        IFW32FALSE_EXIT(this->CreateJobEntry(pNextEntry));
        INTERNAL_ERROR_CHECK(pNextEntry != NULL);

        //
        // Name and full path of test directory
        //
        IFW32FALSE_EXIT(pNextEntry->m_buffTestDirectory.Win32Assign(buffFoundName));
        IFW32FALSE_EXIT(pNextEntry->m_buffTestName.Win32Assign(
            FindData.cFileName,
            ::wcslen(FindData.cFileName)));

        //
        // And now have it load settings
        //
        IFW32FALSE_EXIT(buffFoundName.Win32AppendPathElement(
            this->GetIniFileName(),
            ::wcslen(this->GetIniFileName())));
        IFW32FALSE_EXIT(pNextEntry->LoadFromSettingsFile(buffFoundName));

        //
        // So far, so good - add it to the list of created job entries
        //
        this->m_JobsListed.AddToTail(pNextEntry);
        pNextEntry = NULL;
        
    }
    while ( ::FindNextFileW(Finder, &FindData) );

    if (::FusionpGetLastWin32Error() != ERROR_NO_MORE_FILES)
        goto Exit;

    //
    // Outward bound?
    //
    if ( pulJobsFound )
        *pulJobsFound = static_cast<ULONG>(this->m_JobsListed.GetEntryCount());

    FN_EPILOG
}