/**
* @file Utils.h
* @author Bartosz Janiak
* 
* @brief Contains the definition of Utils class.
*
* Copyright © 2009-2011 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 "TStd.h"
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/find.hpp>
#include <boost/algorithm/string/erase.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/scoped_array.hpp>
#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/posix_time/conversion.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <shlwapi.h>

#define LOGGING_EXPAND_VARIABLES_STRING_SIZE SHRT_MAX

namespace Nvidia {
namespace Logging {

class Utils
{
public:
    /// @brief Returns current value of the performance counter, in milliseconds.
    static long long GetCurrentTimestamp();
    
    /// @brief Returns the first value of the performance counter obtained during lifetime of this process, in milliseconds.
    static long long GetFirstTimestamp();

    ///@brief Returns the start time in terms of a FILETIME structure
    static const boost::posix_time::ptime &GetStartTime();
    
    /**
     * @brief Strips the passed type name from template parameters and modifiers to generate a short but meaningful type name.
     *
     * Type names returned by this function are used by XMLLogPrinter as names of the nodes, so they shouldn't contain
     * any special characters.
     */
    static std::tstring GenerateHumanReadableTypeName(std::tstring typeName);

    /// @brief Returns a copy of the passed string with all letters capitalized.
    static std::tstring CapitalizeString(std::tstring string);

    /// @brief Gets the name of the executable of this process, without the leading path and returns success or failure.
    /// asserts on failure.
    static std::tstring GetProcessName();

    /// @brief Expands the environment variables and places them in outString.
    /// Asserts on failure
    static void ExpandEnvironmentVariables(std::tstring& string);

    /// @brief Max function. Included for compatibility with both the projects that include <windows.h> and those that don't.
    template<typename T>
    static T Maximum(T a, T b)
    {
        return a > b ? a : b;
    }

private:
    /**
     * @brief Uses the creation time of the process to determine the '0'
     * of the first time stamp.
     */
    static long long FindFirstTimestamp();

    /**
     * @brief Calls Windows to find the creation time of the process. If
     * there is an error a "0" time is returned.
     */
    static FILETIME QueryStartFileTime();
};

inline long long Utils::GetCurrentTimestamp()
{
    static long long ignored = GetFirstTimestamp(); //We need that to make sure that the start timestamp stored as a static in GetFirstTimestamp is really first.
    static LARGE_INTEGER frequency;
    static BOOL result = QueryPerformanceFrequency(&frequency);
    LARGE_INTEGER now;
    QueryPerformanceCounter(&now);
    return (now.QuadPart * 1000LL) / frequency.QuadPart;
}

inline long long Utils::GetFirstTimestamp()
{
    static long long timestamp = FindFirstTimestamp();
    return timestamp;
}

inline const boost::posix_time::ptime &Utils::GetStartTime()
{
    using namespace boost::posix_time;
    static ptime startFileTime = from_ftime<ptime>(QueryStartFileTime());
    return startFileTime;
}

inline FILETIME Utils::QueryStartFileTime()
{
    FILETIME creationTime = {0};
    FILETIME exitTime = {0};
    FILETIME kernelTime = {0};
    FILETIME userTime = {0};
    HANDLE currentProcess = GetCurrentProcess();
    BOOL querySuccess = GetProcessTimes(currentProcess, &creationTime, &exitTime, &kernelTime, &userTime);
    assert(!!querySuccess || "GetProcessTimes failed for current process. This should be impossible.");
    DBG_UNREFERENCED_LOCAL_VARIABLE(querySuccess);
    return creationTime;
}

inline long long Utils::FindFirstTimestamp()
{
    using namespace boost::posix_time;
    LARGE_INTEGER frequency;
    QueryPerformanceFrequency(&frequency);
    LARGE_INTEGER start;
    QueryPerformanceCounter(&start);
    long long firstTimestamp = (start.QuadPart * 1000LL) / frequency.QuadPart;

    const ptime &startTime = GetStartTime();
    ptime now = microsec_clock::universal_time();
    time_duration startTimeToNow = now - startTime;

    firstTimestamp -= startTimeToNow.total_milliseconds();
    return firstTimestamp;
}

inline std::tstring Utils::GenerateHumanReadableTypeName(std::tstring typeName)
{
    boost::algorithm::replace_all(typeName, _T("class"), _T(" "));
    boost::algorithm::replace_all(typeName, _T("*"), _T(" "));
    boost::algorithm::replace_all(typeName, _T("const"), _T(" "));

    std::tstring::iterator iterator = boost::algorithm::find_first(typeName, _T("<")).begin();
    
    if ( iterator != typeName.end() )
    {
        int firstTemplate = (int)(iterator - typeName.begin());
        boost::algorithm::erase_tail(typeName, (int)typeName.size() - firstTemplate);
    }

    iterator = boost::algorithm::find_last(typeName, _T("::")).begin();
    if ( iterator != typeName.end() )
    {
        int lastNamespace = (int)(iterator - typeName.begin());
        boost::algorithm::erase_head(typeName, lastNamespace + 2);
    }
    
    boost::algorithm::trim(typeName);
    return typeName;
}

inline std::tstring Utils::CapitalizeString(std::tstring string)
{
    std::transform(string.begin(), string.end(), string.begin(), _totupper);
    return string;
}

inline std::tstring Utils::GetProcessName()
{
    TCHAR processName[MAX_PATH] = _T("DefaultProcess");
    int returnCode = GetModuleFileName(NULL, processName, MAX_PATH);
    DBG_UNREFERENCED_LOCAL_VARIABLE(returnCode);
    assert (returnCode > 0 && returnCode < MAX_PATH );
    return PathFindFileName(processName);
}

inline void Utils::ExpandEnvironmentVariables(std::tstring& string)
{
    std::auto_ptr<TCHAR> buffer(new TCHAR[LOGGING_EXPAND_VARIABLES_STRING_SIZE]);  
    //TODO: http://msdn.microsoft.com/en-us/library/ms724265%28VS.85%29.aspx reports that this doesn't work
    //properly on ANSI builds
    DWORD result = ExpandEnvironmentStrings(string.c_str(), buffer.get(), LOGGING_EXPAND_VARIABLES_STRING_SIZE);
    DBG_UNREFERENCED_LOCAL_VARIABLE(result);
    assert ( result != 0 && result < LOGGING_EXPAND_VARIABLES_STRING_SIZE );
    string = buffer.get();
}



}
}