//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "stdafx.h"
#include <sys/stat.h>

#define MAX_URI_LENGTH 512

//TODO: x-plat definitions
#ifdef _WIN32
#define TTD_MAX_FILE_LENGTH MAX_PATH
#define TTD_HOST_PATH_SEP "\\"
#else
#define TTD_MAX_FILE_LENGTH MAX_URI_LENGTH
#define TTD_HOST_PATH_SEP "/"
#endif

void TTDHostBuildCurrentExeDirectory(char* path, size_t* pathLength, size_t bufferLength)
{
    char exePath[TTD_MAX_FILE_LENGTH];
    PlatformAgnostic::SystemInfo::GetBinaryLocation(exePath, TTD_MAX_FILE_LENGTH);

    size_t i = strlen(exePath) - 1;
    while (exePath[i] != TTD_HOST_PATH_SEP[0] && i != 0)
    {
        --i;
    }
    if (i == 0)
    {
        fwprintf(stderr, _u("Can't get current exe directory"));
        exit(1);
    }
    if (i + 2 > bufferLength)
    {
        fwprintf(stderr, _u("Don't overflow path buffer during copy"));
        exit(1);
    }
    memcpy_s(path, bufferLength, exePath, i + 1);
    *pathLength = i + 1;
    path[*pathLength] = '\0';
}

#ifdef _WIN32
int TTDHostMKDir(const char* path, size_t pathLength)
{
    char16 cpath[MAX_PATH];
    LPCUTF8 pathbase = (LPCUTF8)path;

    if(MAX_PATH <= pathLength) //<= to account for null terminator
    {
        wprintf(_u("Don't overflow path buffer during conversion"));
        exit(1);
    }
    utf8::DecodeUnitsIntoAndNullTerminate(cpath, pathbase, pathbase + pathLength);

    return _wmkdir(cpath);
}

JsTTDStreamHandle TTDHostOpen(size_t pathLength, const char* path, bool isWrite)
{
    char16 wpath[MAX_PATH];
    LPCUTF8 pathbase = (LPCUTF8)path;

    if(MAX_PATH <= pathLength) //<= to account for null terminator
    {
        wprintf(_u("Don't overflow path buffer during conversion"));
        exit(1);
    }
    utf8::DecodeUnitsIntoAndNullTerminate(wpath, pathbase, pathbase + pathLength);

    FILE* res = nullptr;
    _wfopen_s(&res, wpath, isWrite ? _u("w+b") : _u("r+b"));

    return (JsTTDStreamHandle)res;
}

#define TTDHostRead(buff, size, handle) fread_s(buff, size, 1, size, (FILE*)handle);
#define TTDHostWrite(buff, size, handle) fwrite(buff, 1, size, (FILE*)handle)
#else
int TTDHostMKDir(const char* path, size_t pathLength)
{
    return mkdir(path, 0700);
}

JsTTDStreamHandle TTDHostOpen(size_t pathLength, const char* path, bool isWrite)
{
    return (JsTTDStreamHandle)fopen(path, isWrite ? "w+b" : "r+b");
}

#define TTDHostRead(buff, size, handle) fread(buff, 1, size, (FILE*)handle)
#define TTDHostWrite(buff, size, handle) fwrite(buff, 1, size, (FILE*)handle)
#endif

int GetPathNameLocation(LPCSTR filename)
{
    int filenameLength = (int) strlen(filename);
    int pos;

    if (filenameLength <= 0)
    {
        return -1;
    }

    for (pos = filenameLength - 1; pos >= 0; pos--)
    {
        char ch = filename[pos];
        if (ch == '/' || ch == '\\') break;
    }

    return pos;
}

inline void pathcpy(char * target, LPCSTR src, uint length)
{
#ifndef _WIN32
    for (int i = 0; i < length; i++)
    {
        if (src[i] == '\\')
        {
            target[i] = '/';
        }
        else
        {
            target[i] = src[i];
        }
    }
#else
    memcpy(target, src, length);
#endif
}

uint ConcatPath(LPCSTR filenameLeft, uint posPathSep, LPCSTR filenameRight, char* buffer, uint bufferLength)
{
    int filenameRightLength = (int) strlen(filenameRight);

    // [ path[/] ] + [filename] + /0
    uint totalLength = posPathSep + filenameRightLength + 1;
    if (buffer == nullptr)
    {
        return totalLength;
    }

    if (bufferLength < totalLength)
    {
        fprintf(stderr, "Error: file path is too long.\n");
        return (uint)-1;
    }

    pathcpy(buffer, filenameLeft, posPathSep);
    buffer += posPathSep;
    pathcpy(buffer, filenameRight, filenameRightLength);
    buffer += filenameRightLength;
    buffer[0] = char(0);
    return totalLength;
}

HRESULT Helpers::LoadScriptFromFile(LPCSTR filenameToLoad, LPCSTR& contents, UINT* lengthBytesOut /*= nullptr*/, std::string* fullPath /*= nullptr*/, bool shouldMute /*=false */)
{
    static char sHostApplicationPathBuffer[MAX_URI_LENGTH];
    static uint sHostApplicationPathBufferLength = (uint) -1;
    char combinedPathBuffer[MAX_URI_LENGTH];

    HRESULT hr = S_OK;
    BYTE * pRawBytes = nullptr;
    BYTE * pRawBytesFromMap = nullptr;
    UINT lengthBytes = 0;
    contents = nullptr;
    FILE * file = NULL;
    size_t bufferLength = 0;

    LPCSTR filename = fullPath == nullptr ? filenameToLoad : LPCSTR(fullPath->c_str());
    if (sHostApplicationPathBufferLength == (uint)-1)
    {
        // consider incoming filename as the host app and base its' path for others
        sHostApplicationPathBufferLength = GetPathNameLocation(filename);
        if (sHostApplicationPathBufferLength == -1)
        {
            // host app has no path info. (it must be located on current folder!)
            sHostApplicationPathBufferLength = 0;
        }
        else
        {
            sHostApplicationPathBufferLength += 1;
            Assert(sHostApplicationPathBufferLength < MAX_URI_LENGTH);
            // save host app's path and fix the path separator for platform
            pathcpy(sHostApplicationPathBuffer, filename, sHostApplicationPathBufferLength);
        }
        sHostApplicationPathBuffer[sHostApplicationPathBufferLength] = char(0);
    }
    else if (filename[0] != '/' && filename[0] != '\\' && fullPath == nullptr) // make sure it's not a full path
    {
        // concat host path and filename
        uint len = ConcatPath(sHostApplicationPathBuffer, sHostApplicationPathBufferLength,
                   filename, combinedPathBuffer, MAX_URI_LENGTH);

        if (len == (uint)-1)
        {
            hr = E_FAIL;
            goto Error;
        }
        filename = combinedPathBuffer;
    }

    // check if have it registered
    AutoString *data;
    if (SourceMap::Find(filenameToLoad, strlen(filenameToLoad), &data) ||
        SourceMap::Find(filename, strlen(filename), &data))
    {
        pRawBytesFromMap = (BYTE*) data->GetString();
        lengthBytes = (UINT) data->GetLength();
    }
    else
    {
        // Open the file as a binary file to prevent CRT from handling encoding, line-break conversions,
        // etc.
        if (fopen_s(&file, filename, "rb") != 0)
        {
            if (!HostConfigFlags::flags.MuteHostErrorMsgIsEnabled && !shouldMute)
            {
#ifdef _WIN32
                DWORD lastError = GetLastError();
                char16 wszBuff[MAX_URI_LENGTH];
                fprintf(stderr, "Error in opening file '%s' ", filename);
                wszBuff[0] = 0;
                if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                    nullptr,
                    lastError,
                    0,
                    wszBuff,
                    _countof(wszBuff),
                    nullptr))
                {
                    fwprintf(stderr, _u(": %s"), wszBuff);
                }
                fwprintf(stderr, _u("\n"));
#elif defined(_POSIX_VERSION)
                fprintf(stderr, "Error in opening file: ");
                perror(filename);
#endif
            }

            IfFailGo(E_FAIL);
        }
    }

    if (file != NULL)
    {
        // Determine the file length, in bytes.
        fseek(file, 0, SEEK_END);
        lengthBytes = ftell(file);
        fseek(file, 0, SEEK_SET);
    }

    if (lengthBytes != 0)
    {
        bufferLength = lengthBytes + sizeof(BYTE);
        pRawBytes = (LPBYTE)malloc(bufferLength);
    }
    else
    {
        bufferLength = 1;
        pRawBytes = (LPBYTE)malloc(bufferLength);
    }

    if (nullptr == pRawBytes)
    {
        fwprintf(stderr, _u("out of memory"));
        IfFailGo(E_OUTOFMEMORY);
    }

    if (lengthBytes != 0)
    {
        if (file != NULL)
        {
            //
            // Read the entire content as a binary block.
            //
            size_t readBytes = fread(pRawBytes, sizeof(BYTE), lengthBytes, file);
            if (readBytes < lengthBytes * sizeof(BYTE))
            {
                IfFailGo(E_FAIL);
            }
        }
        else // from module source register
        {
            // Q: module source is on persistent memory. Why do we use the copy instead?
            // A: if we use the same memory twice, ch doesn't know that during FinalizeCallback free.
            // the copy memory will be freed by the finalizer
            Assert(pRawBytesFromMap);
            memcpy_s(pRawBytes, bufferLength, pRawBytesFromMap, lengthBytes);
        }
    }

    if (pRawBytes)
    {
        pRawBytes[lengthBytes] = 0; // Null terminate it. Could be UTF16
    }

    if (file != NULL)
    {
        //
        // Read encoding to make sure it's supported
        //
        // Warning: The UNICODE buffer for parsing is supposed to be provided by the host.
        // This is not a complete read of the encoding. Some encodings like UTF7, UTF1, EBCDIC, SCSU, BOCU could be
        // wrongly classified as ANSI
        //
#pragma warning(push)
        // suppressing prefast warning that "readable size is bufferLength
        // bytes but 2 may be read" as bufferLength is clearly > 2 in the code that follows
#pragma warning(disable:6385)
        C_ASSERT(sizeof(WCHAR) == 2);
        if (bufferLength > 2)
        {
            __analysis_assume(bufferLength > 2);
#pragma prefast(push)
#pragma prefast(disable:6385, "PREfast incorrectly reports this as an out-of-bound access.");
            if ((pRawBytes[0] == 0xFE && pRawBytes[1] == 0xFF) ||
                (pRawBytes[0] == 0xFF && pRawBytes[1] == 0xFE) ||
                (bufferLength > 4 && pRawBytes[0] == 0x00 && pRawBytes[1] == 0x00 &&
                    ((pRawBytes[2] == 0xFE && pRawBytes[3] == 0xFF) ||
                    (pRawBytes[2] == 0xFF && pRawBytes[3] == 0xFE))))

            {
                // unicode unsupported
                fwprintf(stderr, _u("unsupported file encoding. Only ANSI and UTF8 supported"));
                IfFailGo(E_UNEXPECTED);
            }
#pragma prefast(pop)
        }
#pragma warning(pop)
    }

    contents = reinterpret_cast<LPCSTR>(pRawBytes);

Error:
    if (SUCCEEDED(hr))
    {
        if (lengthBytesOut)
        {
            *lengthBytesOut = lengthBytes;
        }
    }

    if (file != NULL)
    {
        fclose(file);
    }

    if (pRawBytes && reinterpret_cast<LPCSTR>(pRawBytes) != contents)
    {
        free(pRawBytes);
    }

    return hr;
}

LPCWSTR Helpers::JsErrorCodeToString(JsErrorCode jsErrorCode)
{
    bool hasException = false;
    ChakraRTInterface::JsHasException(&hasException);
    if (hasException)
    {
        WScriptJsrt::PrintException("", JsErrorScriptException);
    }

    switch (jsErrorCode)
    {
    case JsNoError:                            return _u("JsNoError");
    // JsErrorCategoryUsage
    case JsErrorCategoryUsage:                 return _u("JsErrorCategoryUsage");
    case JsErrorInvalidArgument:               return _u("JsErrorInvalidArgument");
    case JsErrorNullArgument:                  return _u("JsErrorNullArgument");
    case JsErrorNoCurrentContext:              return _u("JsErrorNoCurrentContext");
    case JsErrorInExceptionState:              return _u("JsErrorInExceptionState");
    case JsErrorNotImplemented:                return _u("JsErrorNotImplemented");
    case JsErrorWrongThread:                   return _u("JsErrorWrongThread");
    case JsErrorRuntimeInUse:                  return _u("JsErrorRuntimeInUse");
    case JsErrorBadSerializedScript:           return _u("JsErrorBadSerializedScript");
    case JsErrorInDisabledState:               return _u("JsErrorInDisabledState");
    case JsErrorCannotDisableExecution:        return _u("JsErrorCannotDisableExecution");
    case JsErrorHeapEnumInProgress:            return _u("JsErrorHeapEnumInProgress");
    case JsErrorArgumentNotObject:             return _u("JsErrorArgumentNotObject");
    case JsErrorInProfileCallback:             return _u("JsErrorInProfileCallback");
    case JsErrorInThreadServiceCallback:       return _u("JsErrorInThreadServiceCallback");
    case JsErrorCannotSerializeDebugScript:    return _u("JsErrorCannotSerializeDebugScript");
    case JsErrorAlreadyDebuggingContext:       return _u("JsErrorAlreadyDebuggingContext");
    case JsErrorAlreadyProfilingContext:       return _u("JsErrorAlreadyProfilingContext");
    case JsErrorIdleNotEnabled:                return _u("JsErrorIdleNotEnabled");
    case JsErrorInObjectBeforeCollectCallback: return _u("JsErrorInObjectBeforeCollectCallback");
    case JsErrorObjectNotInspectable:          return _u("JsErrorObjectNotInspectable");
    case JsErrorPropertyNotSymbol:             return _u("JsErrorPropertyNotSymbol");
    case JsErrorPropertyNotString:             return _u("JsErrorPropertyNotString");
    case JsErrorInvalidContext:                return _u("JsErrorInvalidContext");
    case JsInvalidModuleHostInfoKind:          return _u("JsInvalidModuleHostInfoKind");
    case JsErrorModuleParsed:                  return _u("JsErrorModuleParsed");
    // JsErrorCategoryEngine
    case JsErrorCategoryEngine:                return _u("JsErrorCategoryEngine");
    case JsErrorOutOfMemory:                   return _u("JsErrorOutOfMemory");
    case JsErrorBadFPUState:                   return _u("JsErrorBadFPUState");
    // JsErrorCategoryScript
    case JsErrorCategoryScript:                return _u("JsErrorCategoryScript");
    case JsErrorScriptException:               return _u("JsErrorScriptException");
    case JsErrorScriptCompile:                 return _u("JsErrorScriptCompile");
    case JsErrorScriptTerminated:              return _u("JsErrorScriptTerminated");
    case JsErrorScriptEvalDisabled:            return _u("JsErrorScriptEvalDisabled");
    // JsErrorCategoryFatal
    case JsErrorCategoryFatal:                 return _u("JsErrorCategoryFatal");
    case JsErrorFatal:                         return _u("JsErrorFatal");
    case JsErrorWrongRuntime:                  return _u("JsErrorWrongRuntime");
    // JsErrorCategoryDiagError
    case JsErrorCategoryDiagError:             return _u("JsErrorCategoryDiagError");
    case JsErrorDiagAlreadyInDebugMode:        return _u("JsErrorDiagAlreadyInDebugMode");
    case JsErrorDiagNotInDebugMode:            return _u("JsErrorDiagNotInDebugMode");
    case JsErrorDiagNotAtBreak:                return _u("JsErrorDiagNotAtBreak");
    case JsErrorDiagInvalidHandle:             return _u("JsErrorDiagInvalidHandle");
    case JsErrorDiagObjectNotFound:            return _u("JsErrorDiagObjectNotFound");
    case JsErrorDiagUnableToPerformAction:     return _u("JsErrorDiagUnableToPerformAction");
    default:
        return _u("<unknown>");
        break;
    }
}

void Helpers::LogError(__in __nullterminated const char16 *msg, ...)
{
    va_list args;
    va_start(args, msg);
    wprintf(_u("ERROR: "));
    vfwprintf(stderr, msg, args);
    wprintf(_u("\n"));
    fflush(stdout);
    va_end(args);
}

HRESULT Helpers::LoadBinaryFile(LPCSTR filename, LPCSTR& contents, UINT& lengthBytes, bool printFileOpenError)
{
    HRESULT hr = S_OK;
    contents = nullptr;
    lengthBytes = 0;
    size_t result;
    FILE * file;

    //
    // Open the file as a binary file to prevent CRT from handling encoding, line-break conversions,
    // etc.
    //
    if (fopen_s(&file, filename, "rb") != 0)
    {
        if (printFileOpenError)
        {
            fprintf(stderr, "Error in opening file '%s' ", filename);
#ifdef _WIN32
            DWORD lastError = GetLastError();
            char16 wszBuff[MAX_URI_LENGTH];
            wszBuff[0] = 0;
            if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                nullptr,
                lastError,
                0,
                wszBuff,
                _countof(wszBuff),
                nullptr))
            {
                fwprintf(stderr, _u(": %s"), wszBuff);
            }
#endif
            fprintf(stderr, "\n");
        }
        return E_FAIL;
    }
    // file will not be nullptr if _wfopen_s succeeds
    __analysis_assume(file != nullptr);

    //
    // Determine the file length, in bytes.
    //
    fseek(file, 0, SEEK_END);
    lengthBytes = ftell(file);
    fseek(file, 0, SEEK_SET);
    contents = (LPCSTR)HeapAlloc(GetProcessHeap(), 0, lengthBytes);
    if (nullptr == contents)
    {
        fwprintf(stderr, _u("out of memory"));
        IfFailGo(E_OUTOFMEMORY);
    }
    //
    // Read the entire content as a binary block.
    //
    result = fread((void*)contents, sizeof(char), lengthBytes, file);
    if (result != lengthBytes)
    {
        fwprintf(stderr, _u("Read error"));
        IfFailGo(E_FAIL);
    }

Error:
    fclose(file);
    if (contents && FAILED(hr))
    {
        HeapFree(GetProcessHeap(), 0, (void*)contents);
        contents = nullptr;
    }

    return hr;
}

void Helpers::TTReportLastIOErrorAsNeeded(BOOL ok, const char* msg)
{
    if(!ok)
    {
#ifdef _WIN32
        DWORD lastError = GetLastError();
        LPTSTR pTemp = NULL;
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, lastError, 0, (LPTSTR)&pTemp, 0, NULL);
        fwprintf(stderr, _u("Error is: %s\n"), pTemp);
        LocalFree(pTemp);
#else
        fprintf(stderr, "Error is: %i %s\n", errno, strerror(errno));
#endif
        fprintf(stderr, "Message is: %s\n", msg);

        AssertMsg(false, "IO Error!!!");
        exit(1);
    }
}

//We assume bounded ascii path length for simplicity
#define MAX_TTD_ASCII_PATH_EXT_LENGTH 64

void Helpers::CreateTTDDirectoryAsNeeded(size_t* uriLength, char* uri, const char* asciiDir1, const wchar* asciiDir2)
{
    if(*uriLength + strlen(asciiDir1) + wcslen(asciiDir2) + 2 > MAX_URI_LENGTH || strlen(asciiDir1) >= MAX_TTD_ASCII_PATH_EXT_LENGTH || wcslen(asciiDir2) >= MAX_TTD_ASCII_PATH_EXT_LENGTH)
    {
        wprintf(_u("We assume bounded MAX_URI_LENGTH for simplicity.\n"));
        wprintf(_u("%S, %S, %ls\n"), uri, asciiDir1, asciiDir2);
        exit(1);
    }

    int success = 0;
    int extLength = 0;

    extLength = sprintf_s(uri + *uriLength, MAX_TTD_ASCII_PATH_EXT_LENGTH, "%s%s", asciiDir1, TTD_HOST_PATH_SEP);
    if(extLength == -1 || MAX_URI_LENGTH < (*uriLength) + extLength)
    {
        wprintf(_u("Failed directory extension 1.\n"));
        wprintf(_u("%S, %S, %ls\n"), uri, asciiDir1, asciiDir2);
        exit(1);
    }
    *uriLength += extLength;

    success = TTDHostMKDir(uri, *uriLength);
    if(success != 0)
    {
        //we may fail because someone else created the directory -- that is ok
        Helpers::TTReportLastIOErrorAsNeeded(errno == EEXIST, "Failed to create directory");
    }

    char realAsciiDir2[MAX_TTD_ASCII_PATH_EXT_LENGTH];
    size_t asciiDir2Length = wcslen(asciiDir2) + 1;
    for(size_t i = 0; i < asciiDir2Length; ++i)
    {
        if(asciiDir2[i] > CHAR_MAX)
        {
            wprintf(_u("Test directory names can only include ascii chars.\n"));
            exit(1);
        }
        realAsciiDir2[i] = (char)asciiDir2[i];
    }

    extLength = sprintf_s(uri + *uriLength, MAX_TTD_ASCII_PATH_EXT_LENGTH, "%s%s", realAsciiDir2, TTD_HOST_PATH_SEP);
    if(extLength == -1 || MAX_URI_LENGTH < *uriLength + extLength)
    {
        wprintf(_u("Failed directory create 2.\n"));
        wprintf(_u("%S, %S, %ls\n"), uri, asciiDir1, asciiDir2);
        exit(1);
    }
    *uriLength += extLength;

    success = TTDHostMKDir(uri, *uriLength);
    if(success != 0)
    {
        //we may fail because someone else created the directory -- that is ok
        Helpers::TTReportLastIOErrorAsNeeded(errno == EEXIST, "Failed to create directory");
    }
}

void Helpers::GetTTDDirectory(const wchar* curi, size_t* uriLength, char* uri, size_t bufferLength)
{
    TTDHostBuildCurrentExeDirectory(uri, uriLength, bufferLength);

    Helpers::CreateTTDDirectoryAsNeeded(uriLength, uri, "_ttdlog", curi);
}

JsTTDStreamHandle CALLBACK Helpers::TTCreateStreamCallback(size_t uriLength, const char* uri, size_t asciiNameLength, const char* asciiName, bool read, bool write)
{
    AssertMsg((read | write) & (!read | !write), "Read/Write streams not supported yet -- defaulting to read only");

    if(uriLength + asciiNameLength + 1 > MAX_URI_LENGTH)
    {
        wprintf(_u("We assume bounded MAX_URI_LENGTH for simplicity."));
        exit(1);
    }

    char path[MAX_URI_LENGTH];
    memset(path, 0, MAX_URI_LENGTH);

    memcpy_s(path, MAX_URI_LENGTH, uri, uriLength);
    memcpy_s(path + uriLength, MAX_URI_LENGTH - uriLength, asciiName, asciiNameLength);

    JsTTDStreamHandle res = TTDHostOpen(uriLength + asciiNameLength, path, write);
    if(res == nullptr)
    {
        fprintf(stderr, "Failed to open file: %s\n", path);
    }

    Helpers::TTReportLastIOErrorAsNeeded(res != nullptr, "Failed File Open");
    return res;
}

bool CALLBACK Helpers::TTReadBytesFromStreamCallback(JsTTDStreamHandle handle, byte* buff, size_t size, size_t* readCount)
{
    AssertMsg(handle != nullptr, "Bad file handle.");

    if(size > MAXDWORD)
    {
        *readCount = 0;
        return false;
    }

    BOOL ok = FALSE;
    *readCount = TTDHostRead(buff, size, (FILE*)handle);
    ok = (*readCount != 0);

    Helpers::TTReportLastIOErrorAsNeeded(ok, "Failed Read!!!");

    return ok ? true : false;
}

bool CALLBACK Helpers::TTWriteBytesToStreamCallback(JsTTDStreamHandle handle, const byte* buff, size_t size, size_t* writtenCount)
{
    AssertMsg(handle != nullptr, "Bad file handle.");

    if(size > MAXDWORD)
    {
        *writtenCount = 0;
        return false;
    }

    BOOL ok = FALSE;
    *writtenCount = TTDHostWrite(buff, size, (FILE*)handle);
    ok = (*writtenCount == size);

    Helpers::TTReportLastIOErrorAsNeeded(ok, "Failed Read!!!");

    return ok ? true : false;
}

void CALLBACK Helpers::TTFlushAndCloseStreamCallback(JsTTDStreamHandle handle, bool read, bool write)
{
    fflush((FILE*)handle);
    fclose((FILE*)handle);
}

void GetBinaryPathWithFileNameA(char *path, const size_t buffer_size, const char* filename)
{
    char fullpath[_MAX_PATH];
    char drive[_MAX_DRIVE];
    char dir[_MAX_DIR];

    char modulename[_MAX_PATH];
    PlatformAgnostic::SystemInfo::GetBinaryLocation(modulename, _MAX_PATH);
    _splitpath_s(modulename, drive, _MAX_DRIVE, dir, _MAX_DIR, nullptr, 0, nullptr, 0);
    _makepath_s(fullpath, drive, dir, filename, nullptr);

    size_t len = strlen(fullpath);
    if (len < buffer_size)
    {
        memcpy(path, fullpath, len * sizeof(char));
    }
    else
    {
        len = 0;
    }
    path[len] = char(0);
}
