/**
 * @file FileLogManagerTest.h
 * @author Bartosz Janiak
 * 
 * @brief
 *
 * Copyright © 2009 NVIDIA Corporation. All rights reserved.
 *
 * NVIDIA Corporation and its licensors retain all intellectual property and proprietary
 * rights in and to this software, related documentation and any modifications thereto. Any
 * use, reproduction, disclosure or distribution of this software and related documentation
 * without express license agreement from NVIDIA Corporation is strictly prohibited.
 */

#pragma once
#include "MockWinApi.h"
#include "cxxtest/TestSuite.h"
#include "TestObjects.h"
#include <TBoostFilesystem.h>
#include <Logging.h>
#include <FileLogManager.h>
#include <RegistryKey.h>
#include <boost/thread.hpp>

#include <windows.h>
#include <Shfolder.h>
#include <shlobj.h>

namespace Nvidia {
namespace Logging {
namespace Test {

class FileLogManagerTest : public CxxTest::TestSuite {
public:
    boost::filesystem::tpath m_originalPath;
    boost::filesystem::tpath m_duringTestsPath;

    FileLogManagerTest() :
        m_originalPath(GetAllUsersDocumentsFolder() + _T("\\NvidiaLogging")),
        m_duringTestsPath(GetAllUsersDocumentsFolder() + _T("\\NvidiaLoggingMovedForTesting")) {}

    void setUp()
    {
        if ( boost::filesystem::exists(m_originalPath) && !boost::filesystem::exists(m_duringTestsPath) )
        {
            boost::filesystem::rename(m_originalPath, m_duringTestsPath);
        }

    }

    void tearDown()
    {
        boost::filesystem::remove_all(m_originalPath);

        if ( boost::filesystem::exists(m_duringTestsPath) )
            boost::filesystem::rename(m_duringTestsPath, m_originalPath);
    }

    /*  [HKEY_LOCAL_MACHINE\Software\NVIDIA Corporation\Logging\Definitions\LogManagers\MyManager3]
        "ClassName"="FileLogManager"
        "PathPrefix"="c:\\testLogging\\moreTest\\test"
        "AppendProcessNameToPathPrefix"=dword:00000001
        "MaxFileCount"=dword:00000003
        "MaxFileSize"=dword:00001000
    */
    void TestRegistryConstructor()
    {
        RegistryKey key = RegistryKey(HKEY_LOCAL_MACHINE).GetSubKey(_T("Software")).GetSubKey(_T("NVIDIA Corporation")).GetSubKey(_T("Logging"))
            .GetSubKey(_T("Definitions")).GetSubKey(_T("LogManagers")).GetSubKey(_T("MyManager3"));

        FileLogManager logManager(key);

        std::tstring processName = Utils::GetProcessName();
        TS_ASSERT_EQUALS(logManager.m_pathPrefix, std::tstring(_T("c:\\testLogging\\moreTest\\test.")) + processName + _T(".log")); 
        TS_ASSERT_EQUALS(logManager.m_maxFileCount, 3); 
        TS_ASSERT_EQUALS(logManager.m_maxFileSize, 0x1000);
    }
    
    static std::tstring GetAllUsersDocumentsFolder()
    {
        TCHAR buffer[MAX_PATH];
        if ( SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, SHGFP_TYPE_CURRENT, buffer) != S_OK )
            throw std::runtime_error("SHGetFolderPath failed");
        return buffer;
    }

    void TestDefaultConstructor()
    {
        FileLogManager logManager;

        std::tstring expectedPathPrefix(GetAllUsersDocumentsFolder() + _T("\\NvidiaLogging\\Log."));
        std::tstring processName = Utils::GetProcessName();
        expectedPathPrefix += processName;
        expectedPathPrefix += _T(".log");

        TS_ASSERT_EQUALS(logManager.m_pathPrefix, expectedPathPrefix); 
        TS_ASSERT_EQUALS(logManager.m_maxFileCount, 10); 
        TS_ASSERT_EQUALS(logManager.m_maxFileSize, 1000000);

        for ( int i = 0 ; i < 100000 ; i++ )
            logManager.ManageMessage(std::tstring(_T("aaaaaaaa")));
        //Written 10 bytes per message (8 * 'a' + "\r\n"), 100 000 times, that's 1 000 000 bytes

        TS_ASSERT(boost::filesystem::exists(boost::filesystem::tpath(expectedPathPrefix)));
        TS_ASSERT(!boost::filesystem::exists(boost::filesystem::tpath(expectedPathPrefix + _T("1"))));

        logManager.ManageMessage(std::tstring(_T("aaaaaaaa"))); // this pushes it over
        TS_ASSERT(boost::filesystem::exists(boost::filesystem::tpath(expectedPathPrefix)));
        TS_ASSERT(!boost::filesystem::exists(boost::filesystem::tpath(expectedPathPrefix + _T("1"))));

        logManager.ManageMessage(std::tstring(_T("aaaaaaaa"))); // this time there's a rotation

        TS_ASSERT(boost::filesystem::exists(boost::filesystem::tpath(expectedPathPrefix)));
        TS_ASSERT(boost::filesystem::exists(boost::filesystem::tpath(expectedPathPrefix + _T("1"))));
        TS_ASSERT(!boost::filesystem::exists(boost::filesystem::tpath(expectedPathPrefix + _T("2"))));

        TS_ASSERT_EQUALS(boost::filesystem::file_size(boost::filesystem::tpath(expectedPathPrefix)), 10);
        TS_ASSERT_EQUALS(boost::filesystem::file_size(boost::filesystem::tpath(expectedPathPrefix + _T("1"))), 1000010);
        
        logManager.ManageMessage(std::tstring(_T("aaaaaaaa")));

        TS_ASSERT(boost::filesystem::exists(boost::filesystem::tpath(expectedPathPrefix)));
        TS_ASSERT(boost::filesystem::exists(boost::filesystem::tpath(expectedPathPrefix + _T("1"))));
        TS_ASSERT(!boost::filesystem::exists(boost::filesystem::tpath(expectedPathPrefix + _T("2"))));

        TS_ASSERT_EQUALS(boost::filesystem::file_size(boost::filesystem::tpath(expectedPathPrefix)), 20);
        TS_ASSERT_EQUALS(boost::filesystem::file_size(boost::filesystem::tpath(expectedPathPrefix + _T("1"))), 1000010);
    }

    void testNamedWindowsMutexNormal()
    {
        std::tstring mutexName = _T("TestMutex");
        NamedWindowsMutex myMutex(mutexName);
        bool gotMutex = TryGetMutexLock(mutexName);
        TS_ASSERT(gotMutex);
        myMutex.Lock();
        gotMutex = TryGetMutexLock(mutexName);
        TS_ASSERT(!gotMutex);
        myMutex.Unlock();
        gotMutex = TryGetMutexLock(mutexName);
        TS_ASSERT(gotMutex);
    }

    void testNamedWindowsMutexBadName()
    {
        TS_ASSERT_THROWS(
            NamedWindowsMutex myMutex(_T("\\Te\\st\\M\\tex")),
            std::runtime_error
            );
    }

    void testNamedWindowsMutexBadLock()
    {
        NamedWindowsMutex myMutex(_T("TestMutex"));
        CloseHandle(myMutex.m_mutex);
        TS_ASSERT_THROWS(myMutex.Lock(), std::runtime_error);
        myMutex.m_mutex = NULL;
    }

    void testScopeLock()
    {
        std::tstring mutexName = _T("TestMutex");
        bool gotMutex = false;
        NamedWindowsMutex myMutex(mutexName);
        {
            ScopedMutexLock lock(myMutex);
            gotMutex = TryGetMutexLock(mutexName);
            TS_ASSERT(!gotMutex);
        }
        gotMutex = TryGetMutexLock(mutexName);
        TS_ASSERT(gotMutex);
    }

    void testWindowsFileAppender()
    {
        using namespace boost::filesystem;
        tpath logPath = m_originalPath / _T("test.log");
        if (exists(logPath))
        {
            boost::filesystem::remove(logPath);
        }
        create_directories(m_originalPath);
        WindowsFileAppender file;
        TS_ASSERT(file.Open(logPath.file_string(), WindowsFileAppender::OpenFlags_Buffered));
        WindowsFileAppender file2;
        TS_ASSERT(file2.Open(logPath.file_string(), WindowsFileAppender::OpenFlags_Buffered));
        TS_ASSERT(exists(logPath));
        TS_ASSERT_EQUALS(file_size(logPath),0);
        TS_ASSERT_EQUALS(file.GetSize(), 0);
        TS_ASSERT_EQUALS(file2.GetSize(), 0);
        file.Append(_T("Test1"));
        file2.Append(_T("Test2"));
        TS_ASSERT_EQUALS(file_size(logPath),10);
        TS_ASSERT_EQUALS(file.GetSize(), 10);
        TS_ASSERT_EQUALS(file2.GetSize(), 10);
    }

    void testWindowsFileAppenderNotOpen()
    {
        TS_ASSERT_THROWS_NOTHING(
            WindowsFileAppender file;
            );
        WindowsFileAppender file2;
        TS_ASSERT_THROWS(
            file2.Append(_T("Anything")),
            std::runtime_error
            );
    }

    void testCreateFlags()
    {
        using namespace boost::filesystem;
        tpath logPath = m_originalPath / _T("test.log");
        if (exists(logPath))
        {
            boost::filesystem::remove(logPath);
        }
        create_directories(m_originalPath);
        {
            WindowsFileAppender file;
            TS_ASSERT(file.Open(logPath.file_string(), WindowsFileAppender::OpenFlags_Buffered));
            TS_ASSERT((MockWinApi::m_createFileFlags & FILE_FLAG_WRITE_THROUGH) != FILE_FLAG_WRITE_THROUGH);
        }
        boost::filesystem::remove(logPath);
        {
            WindowsFileAppender file;
            TS_ASSERT(file.Open(logPath.file_string(), WindowsFileAppender::OpenFlags_WriteThrough));
            TS_ASSERT((MockWinApi::m_createFileFlags & FILE_FLAG_WRITE_THROUGH) == FILE_FLAG_WRITE_THROUGH);
        }
    }

    void testWindowsFileAppenderBadPath()
    {
        WindowsFileAppender file;
        bool opened = file.Open(_T("abcdefg:\\bad\\path\\for\\anything\\\\blarg!?!"), WindowsFileAppender::OpenFlags_WriteThrough);
        TS_ASSERT(!opened);
    }

    static bool TryGetMutexLock(const std::tstring &mutexName)
    {
        using namespace boost;
        bool gotMutex = false;
        thread mutexThread(bind(&GetMutexLock, cref(mutexName), ref(gotMutex)));
        mutexThread.join();
        return gotMutex;
    }

    static void GetMutexLock(const std::tstring &mutexName, bool &gotMutex)
    {
        std::tstring fullMutexName(_T("Global\\"));
        fullMutexName += mutexName;
        fullMutexName += _T("{C40CFCD4-C757-4139-A4DA-7CB51A8DBF80}");
        HANDLE testMutex = ::CreateMutex(NULL, FALSE, fullMutexName.c_str());
        DWORD ret = WaitForSingleObject(testMutex, 1);
        gotMutex = (ret == WAIT_OBJECT_0);
        if (gotMutex)
        {
            ReleaseMutex(testMutex);
        }
        CloseHandle(testMutex);
    }
};

}
}
}

using namespace Nvidia::Logging::Test;
