/*========================== begin_copyright_notice ============================
Copyright (C) 2021 Intel Corporation

SPDX-License-Identifier: MIT
============================= end_copyright_notice ===========================*/

/*!
 * @file This file implements general utilities to support GTReplay
 */
#include <thread>
#include <chrono>
#include <fstream>
#include <algorithm>
#include <cstring>

#include "gtreplay_crt.h"

/***********************************************
 *
 * File manipulation functions
 *
 ***********************************************/
/*
 * Opening a binary file
 *
 * @param[out] file - pointer to the file handle
 * @param[in]  name - the name of the file to be open
 * @return true if the operation succeeds, false otherwise
 */
bool OpenBinFile(FileHandle* file, string& name)
{
    std::ifstream* f = new std::ifstream(name.c_str(), std::ios::binary);

    if (!f || !f->is_open())
    {
        delete f;
        return false;
    }

    *file = (FileHandle)f;
    
    return true;
}

/*
 * Obtaining the size in bytes of a file
 *
 * @return the size in bytes of the file
 * @param[in] file - a file handle
 */
size_t GetFileSize(FileHandle file)
{
    std::ifstream& f = *(std::ifstream*)file;

    size_t size;
    f.seekg(0, std::ios::end);
    size = static_cast<size_t>(f.tellg());
    f.seekg(0, std::ios::beg);

    return size;
}

/*
 * Read a given number of characters from a file and stores them in a memory array
 *
 * @param[in] file - a file handle
 * @param[in] buffer - a pointer to an array of characters where to read the file content
 * @param[in] size   - the number of characters to read from the file
 * @note the caller is responsible to allocate a buffer big enough to include the required content
 */
void ReadBinFile(FileHandle file, char* buffer, size_t size)
{
    std::ifstream& f = *(std::ifstream*)file;

    f.read(buffer, size);
}

/*
 * Closing a binary file
 *
 * @param[in] file - a file handle
 */
void CloseBinFile(FileHandle file)
{
    std::ifstream* f = (std::ifstream*)file;

    if (f)
    {
        f->close();
    }

    delete f;
}


/***********************************************
 *
 * Process and threads manipulation functions
 *
 ***********************************************/

/*
 * Put the process into a sleep state for a given number of seconds
 *
 * @param[in] file - a file handle
 */
void SleepSeconds(uint32_t seconds)
{
    std::this_thread::sleep_for(std::chrono::seconds(seconds));
}


/*
 * Return the current thread ID
 *
 */
uint32_t CurrentThreadID()
{
    return (uint32_t)std::hash<std::thread::id>{}(std::this_thread::get_id());
}


/*
 * Create a given number of host threads with a specified entry point
 *
 * @param[in] numOfThreads - the number of threads to create
 * @param[in] func         - the function entry point to each thread
 * @param[in] param        - a parameter to be provided to the entry point function
 * @return an array of thread handles
 */
ThreadHandles CreateHostThreads(uint32_t numOfThreads, RunHostThreadFunc func, void* param)
{
    std::vector<std::thread>* threads = new std::vector<std::thread>;

    if (!threads)
    {
        return (ThreadHandles)NULL;
    }

    for (uint32_t i = 0; i < numOfThreads; i++)
    {
        threads->emplace_back(func, param);
    }

    return (ThreadHandles)threads;
}


/*
 * Waiting till all given threads finish
 *
 * @param[in] numOfThreads - the number of threads to create
 * @param[in] t            - an array of thread handles
 */
void JoinHostThreads(uint32_t numOfThreads, ThreadHandles th)
{
    std::vector<std::thread>* ptr = (std::vector<std::thread>*)th;
    std::vector<std::thread>& threads = *ptr;

    for (auto& t : threads)
    {
        t.join();
    }

    threads.erase(threads.begin(), threads.end());

    delete ptr;
}


/***********************************************
 *
 * String manipulation
 *
 ***********************************************/

/*
 * Convert decimal number into string
 *
 * @param[in] num   - the number 
 * @param[in] width - amount of format characters
 */
string DecStr(uint32_t& num, unsigned int width)
{
    std::stringstream strm;
    strm << std::setw(width) << num;
    return strm.str();
}

static
string HexStrInternal(uint64_t num, string prefix, int width)
{
    std::stringstream strm;
    strm << prefix << std::hex << std::setw(width) << std::setfill('0') << num;
    return strm.str();
}

/*
 * Convert hexadecimal number into string
 *
 * @param[in] num   - the number 
 * @param[in] width - amount of format characters
 */
string HexStr(uint64_t num, int width)
{
    return HexStrInternal(num, "0x", width);
}

/*
 * Convert a pointer into string
 *
 * @param[in] ptr   - a pointer 
 * @param[in] width - amount of format characters
 */
string HexStr(void* ptr, int width)
{
    std::stringstream strm;
    strm << std::hex << std::setw(width) << std::setfill('0') << ptr;
    return strm.str();
}

/*
 * Convert hexadecimal number into string with prefix
 *
 * @param[in] num    - the number 
 * @param[in] prefix - the prefix 
 * @param[in] width  - amount of format characters
 */
string HexStr(uint64_t num, string prefix, int width)
{
    return HexStrInternal(num, prefix, width);
}

std::string ToLower(const std::string& str)
{
    std::string temp(str);
    std::transform(temp.begin(), temp.end(), temp.begin(), [](unsigned char c) -> unsigned char { return (unsigned char)std::tolower(c); });
    return temp;
}

std::string ToUpper(const std::string& str)
{
    std::string temp(str);
    std::transform(temp.begin(), temp.end(), temp.begin(), [](unsigned char c) -> unsigned char { return (unsigned char)std::toupper(c); });
    return temp;
}

void SplitString(const std::string& str, const char delim, std::vector<std::string>& tokens)
{
    std::stringstream stream(str);
    std::string token;

    while (!stream.eof())
    {
        std::getline(stream, token, delim);
        tokens.push_back(token);
    }
}

bool StrToInt(const std::string& str, uint32_t& res)
{
    if (std::strspn(str.c_str(), "0123456789") == str.size())
    {
        res = std::atoi(str.c_str());
        return true;
    }
    return false;
}

// Return a parh separator character
char PathSeparator()
{
#if defined(TARGET_WINDOWS)
    static const char sep = '\\';
#else
    static const char sep = '/';
#endif
    return sep;
}