//===-- MIUtilFileStd.cpp ---------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

// Third party headers
#include <stdio.h>
#include <assert.h>
#include <string.h> // For strerror()
#include <cerrno>

// In-house headers:
#include "MICmnResources.h"
#include "MIUtilFileStd.h"
#include "lldb/Host/FileSystem.h"

#include "llvm/Support/ConvertUTF.h"

//++ ------------------------------------------------------------------------------------
// Details: CMIUtilFileStd constructor.
// Type:    Method.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
CMIUtilFileStd::CMIUtilFileStd()
    : m_fileNamePath(CMIUtilString())
    , m_pFileHandle(nullptr)
#if defined(_MSC_VER)
    , m_constCharNewLine("\r\n")
#else
    , m_constCharNewLine("\n")
#endif // #if defined( _MSC_VER )
    , m_bFileError(false)
{
}

//++ ------------------------------------------------------------------------------------
// Details: CMIUtilFileStd destructor.
// Type:    Method.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
CMIUtilFileStd::~CMIUtilFileStd()
{
    Close();
}

//++ ------------------------------------------------------------------------------------
// Details: Open file for writing. On the first call to this function after *this object
//          is created the file is either created or replace, from then on open only opens
//          an existing file.
// Type:    Method.
// Args:    vFileNamePath   - (R) File name path.
//          vwrbNewCreated  - (W) True - file recreated, false - file appended too.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIUtilFileStd::CreateWrite(const CMIUtilString &vFileNamePath, bool &vwrbNewCreated)
{
    // Reset
    m_bFileError = false;
    vwrbNewCreated = false;

    if (vFileNamePath.empty())
    {
        m_bFileError = true;
        SetErrorDescription(MIRSRC(IDS_UTIL_FILE_ERR_INVALID_PATHNAME));
        return MIstatus::failure;
    }

    // File is already open so exit
    if (m_pFileHandle != nullptr)
        return MIstatus::success;

#if !defined(_MSC_VER)
    // Open with 'write' and 'binary' mode
    m_pFileHandle = ::fopen(vFileNamePath.c_str(), "wb");
#else
    // Open a file with exclusive write and shared read permissions
    std::wstring path;
    if (llvm::ConvertUTF8toWide(vFileNamePath.c_str(), path))
        m_pFileHandle = ::_wfsopen(path.c_str(), L"wb", _SH_DENYWR);
    else
    {
        errno = EINVAL;
        m_pFileHandle = nullptr;
    }
#endif // !defined( _MSC_VER )

    if (m_pFileHandle == nullptr)
    {
        m_bFileError = true;
        SetErrorDescriptionn(MIRSRC(IDS_UTIL_FILE_ERR_OPENING_FILE), strerror(errno), vFileNamePath.c_str());
        return MIstatus::failure;
    }

    vwrbNewCreated = true;
    m_fileNamePath = vFileNamePath;

    return MIstatus::success;
}

//++ ------------------------------------------------------------------------------------
// Details: Write data to existing opened file.
// Type:    Method.
// Args:    vData - (R) Text data.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIUtilFileStd::Write(const CMIUtilString &vData)
{
    if (vData.size() == 0)
        return MIstatus::success;

    if (m_bFileError)
        return MIstatus::failure;

    if (m_pFileHandle == nullptr)
    {
        m_bFileError = true;
        SetErrorDescriptionn(MIRSRC(IDE_UTIL_FILE_ERR_WRITING_NOTOPEN), m_fileNamePath.c_str());
        return MIstatus::failure;
    }

    // Get the string size
    MIuint size = vData.size();
    if (::fwrite(vData.c_str(), 1, size, m_pFileHandle) == size)
    {
        // Flush the data to the file
        ::fflush(m_pFileHandle);
        return MIstatus::success;
    }

    // Not all of the data has been transferred
    m_bFileError = true;
    SetErrorDescriptionn(MIRSRC(IDE_UTIL_FILE_ERR_WRITING_FILE), m_fileNamePath.c_str());
    return MIstatus::failure;
}

//++ ------------------------------------------------------------------------------------
// Details: Write data to existing opened file.
// Type:    Method.
// Args:    vData       - (R) Text data.
//          vCharCnt    - (R) Text data length.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIUtilFileStd::Write(const char *vpData, const MIuint vCharCnt)
{
    if (vCharCnt == 0)
        return MIstatus::success;

    if (m_bFileError)
        return MIstatus::failure;

    if (m_pFileHandle == nullptr)
    {
        m_bFileError = true;
        SetErrorDescriptionn(MIRSRC(IDE_UTIL_FILE_ERR_WRITING_NOTOPEN), m_fileNamePath.c_str());
        return MIstatus::failure;
    }

    if (::fwrite(vpData, 1, vCharCnt, m_pFileHandle) == vCharCnt)
    {
        // Flush the data to the file
        ::fflush(m_pFileHandle);
        return MIstatus::success;
    }

    // Not all of the data has been transferred
    m_bFileError = true;
    SetErrorDescriptionn(MIRSRC(IDE_UTIL_FILE_ERR_WRITING_FILE), m_fileNamePath.c_str());
    return MIstatus::failure;
}

//++ ------------------------------------------------------------------------------------
// Details: Close existing opened file. Note Close() must must an open!
// Type:    Method.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
void
CMIUtilFileStd::Close()
{
    if (m_pFileHandle == nullptr)
        return;

    ::fclose(m_pFileHandle);
    m_pFileHandle = nullptr;
    // m_bFileError = false; Do not reset as want to remain until next attempt at open or create
}

//++ ------------------------------------------------------------------------------------
// Details: Retrieve state of whether the file is ok.
// Type:    Method.
// Args:    None.
// Return:  True - file ok.
//          False - file has a problem.
// Throws:  None.
//--
bool
CMIUtilFileStd::IsOk() const
{
    return !m_bFileError;
}

//++ ------------------------------------------------------------------------------------
// Details: Status on a file existing already.
// Type:    Method.
// Args:    vFileNamePath.
// Return:  True - Exists.
//          False - Not found.
// Throws:  None.
//--
bool
CMIUtilFileStd::IsFileExist(const CMIUtilString &vFileNamePath) const
{
    if (vFileNamePath.empty())
        return false;

    FILE *pTmp = nullptr;
    pTmp = ::fopen(vFileNamePath.c_str(), "wb");
    if (pTmp != nullptr)
    {
        ::fclose(pTmp);
        return true;
    }

    return false;
}

//++ ------------------------------------------------------------------------------------
// Details: Retrieve the file current carriage line return characters used.
// Type:    Method.
// Args:    None.
// Return:  CMIUtilString & - Text.
// Throws:  None.
//--
const CMIUtilString &
CMIUtilFileStd::GetLineReturn() const
{
    return m_constCharNewLine;
}

//++ ------------------------------------------------------------------------------------
// Details: Given a file name directory path, strip off the filename and return the path.
//          It look for either backslash or forward slash.
// Type:    Method.
// Args:    vDirectoryPath  - (R) Text directory path.
// Return:  CMIUtilString - Directory path.
// Throws:  None.
//--
CMIUtilString
CMIUtilFileStd::StripOffFileName(const CMIUtilString &vDirectoryPath)
{
    const size_t nPos = vDirectoryPath.rfind('\\');
    size_t nPos2 = vDirectoryPath.rfind('/');
    if ((nPos == std::string::npos) && (nPos2 == std::string::npos))
        return vDirectoryPath;

    if (nPos > nPos2)
        nPos2 = nPos;

    const CMIUtilString strPath(vDirectoryPath.substr(0, nPos2).c_str());
    return strPath;
}

//++ ------------------------------------------------------------------------------------
// Details: Return either backslash or forward slash appropriate to the OS this application
//          is running on.
// Type:    Static method.
// Args:    None.
// Return:  char - '/' or '\' character.
// Throws:  None.
//--
char
CMIUtilFileStd::GetSlash()
{
#if !defined(_MSC_VER)
    return '/';
#else
    return '\\';
#endif // !defined( _MSC_VER )
}
