// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

#include "precomp.h"


// Exit macros
#define IniExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
#define IniExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
#define IniExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
#define IniExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
#define IniExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
#define IniExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
#define IniExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_INIUTIL, p, x, e, s, __VA_ARGS__)
#define IniExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_INIUTIL, p, x, s, __VA_ARGS__)
#define IniExitOnNullDebugTrace(p, x, e, s, ...)  ExitOnNullDebugTraceSource(DUTIL_SOURCE_INIUTIL, p, x, e, s, __VA_ARGS__)
#define IniExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_INIUTIL, p, x, s, __VA_ARGS__)
#define IniExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_INIUTIL, e, x, s, __VA_ARGS__)
#define IniExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_INIUTIL, g, x, s, __VA_ARGS__)

const LPCWSTR wzSectionSeparator = L"\\";

struct INI_STRUCT
{
    LPWSTR sczPath; // the path to the INI file to be parsed

    LPWSTR sczOpenTagPrefix; // For regular ini, this would be '['
    LPWSTR sczOpenTagPostfix; // For regular ini, this would be ']'

    LPWSTR sczValuePrefix; // for regular ini, this would be NULL
    LPWSTR sczValueSeparator; // for regular ini, this would be '='

    LPWSTR *rgsczValueSeparatorExceptions;
    DWORD cValueSeparatorExceptions;

    LPWSTR sczCommentLinePrefix; // for regular ini, this would be ';'

    INI_VALUE *rgivValues;
    DWORD cValues;

    LPWSTR *rgsczLines;
    DWORD cLines;

    FILE_ENCODING feEncoding;
    BOOL fModified;
};

const int INI_HANDLE_BYTES = sizeof(INI_STRUCT);

static HRESULT GetSectionPrefixFromName(
    __in_z LPCWSTR wzName,
    __deref_inout_z LPWSTR* psczOutput
    );
static void UninitializeIniValue(
    INI_VALUE *pivValue
    );

extern "C" HRESULT DAPI IniInitialize(
    __out_bcount(INI_HANDLE_BYTES) INI_HANDLE* piHandle
    )
{
    HRESULT hr = S_OK;

    // Allocate the handle
    *piHandle = static_cast<INI_HANDLE>(MemAlloc(sizeof(INI_STRUCT), TRUE));
    IniExitOnNull(*piHandle, hr, E_OUTOFMEMORY, "Failed to allocate ini object");

LExit:
    return hr;
}

extern "C" void DAPI IniUninitialize(
    __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle
    )
{
    INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);

    ReleaseStr(pi->sczPath);
    ReleaseStr(pi->sczOpenTagPrefix);
    ReleaseStr(pi->sczOpenTagPostfix);
    ReleaseStr(pi->sczValuePrefix);
    ReleaseStr(pi->sczValueSeparator);

    for (DWORD i = 0; i < pi->cValueSeparatorExceptions; ++i)
    {
        ReleaseStr(pi->rgsczValueSeparatorExceptions + i);
    }

    ReleaseStr(pi->sczCommentLinePrefix);

    for (DWORD i = 0; i < pi->cValues; ++i)
    {
        UninitializeIniValue(pi->rgivValues + i);
    }
    ReleaseMem(pi->rgivValues);

    ReleaseStrArray(pi->rgsczLines, pi->cLines);

    ReleaseMem(pi);
}

extern "C" HRESULT DAPI IniSetOpenTag(
    __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
    __in_z_opt LPCWSTR wzOpenTagPrefix,
    __in_z_opt LPCWSTR wzOpenTagPostfix
    )
{
    HRESULT hr = S_OK;

    INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);

    if (wzOpenTagPrefix)
    {
        hr = StrAllocString(&pi->sczOpenTagPrefix, wzOpenTagPrefix, 0);
        IniExitOnFailure(hr, "Failed to copy open tag prefix to ini struct: %ls", wzOpenTagPrefix);
    }
    else
    {
        ReleaseNullStr(pi->sczOpenTagPrefix);
    }

    if (wzOpenTagPostfix)
    {
        hr = StrAllocString(&pi->sczOpenTagPostfix, wzOpenTagPostfix, 0);
        IniExitOnFailure(hr, "Failed to copy open tag postfix to ini struct: %ls", wzOpenTagPostfix);
    }
    else
    {
        ReleaseNullStr(pi->sczOpenTagPrefix);
    }

LExit:
    return hr;
}

extern "C" HRESULT DAPI IniSetValueStyle(
    __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
    __in_z_opt LPCWSTR wzValuePrefix,
    __in_z_opt LPCWSTR wzValueSeparator
    )
{
    HRESULT hr = S_OK;

    INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);

    if (wzValuePrefix)
    {
        hr = StrAllocString(&pi->sczValuePrefix, wzValuePrefix, 0);
        IniExitOnFailure(hr, "Failed to copy value prefix to ini struct: %ls", wzValuePrefix);
    }
    else
    {
        ReleaseNullStr(pi->sczValuePrefix);
    }

    if (wzValueSeparator)
    {
        hr = StrAllocString(&pi->sczValueSeparator, wzValueSeparator, 0);
        IniExitOnFailure(hr, "Failed to copy value separator to ini struct: %ls", wzValueSeparator);
    }
    else
    {
        ReleaseNullStr(pi->sczValueSeparator);
    }

LExit:
    return hr;
}

extern "C" HRESULT DAPI IniSetValueSeparatorException(
    __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
    __in_z LPCWSTR wzValueNamePrefix
    )
{
    HRESULT hr = S_OK;
    DWORD dwInsertedIndex = 0;

    INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);

    hr = MemEnsureArraySize(reinterpret_cast<void **>(&pi->rgsczValueSeparatorExceptions), pi->cValueSeparatorExceptions + 1, sizeof(LPWSTR), 5);
    IniExitOnFailure(hr, "Failed to increase array size for value separator exceptions");
    dwInsertedIndex = pi->cValueSeparatorExceptions;
    ++pi->cValueSeparatorExceptions;

    hr = StrAllocString(&pi->rgsczValueSeparatorExceptions[dwInsertedIndex], wzValueNamePrefix, 0);
    IniExitOnFailure(hr, "Failed to copy value separator exception");

LExit:
    return hr;
}

extern "C" HRESULT DAPI IniSetCommentStyle(
    __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
    __in_z_opt LPCWSTR wzLinePrefix
    )
{
    HRESULT hr = S_OK;

    INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);

    if (wzLinePrefix)
    {
        hr = StrAllocString(&pi->sczCommentLinePrefix, wzLinePrefix, 0);
        IniExitOnFailure(hr, "Failed to copy comment line prefix to ini struct: %ls", wzLinePrefix);
    }
    else
    {
        ReleaseNullStr(pi->sczCommentLinePrefix);
    }

LExit:
    return hr;
}

extern "C" HRESULT DAPI IniParse(
    __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
    __in LPCWSTR wzPath,
    __out_opt FILE_ENCODING *pfeEncodingFound
    )
{
    HRESULT hr = S_OK;
    DWORD dwValuePrefixLength = 0;
    DWORD dwValueSeparatorExceptionLength = 0;
    LPWSTR sczContents = NULL;
    LPWSTR sczCurrentSection = NULL;
    LPWSTR sczName = NULL;
    LPWSTR sczNameTrimmed = NULL;
    LPWSTR sczValue = NULL;
    LPWSTR sczValueTrimmed = NULL;
    LPWSTR wzOpenTagPrefix = NULL;
    LPWSTR wzOpenTagPostfix = NULL;
    LPWSTR wzValuePrefix = NULL;
    LPWSTR wzValueNameStart = NULL;
    LPWSTR wzValueSeparator = NULL;
    LPWSTR wzCommentLinePrefix = NULL;
    LPWSTR wzValueBegin = NULL;
    LPCWSTR wzTemp = NULL;

    INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);

    BOOL fSections = (NULL != pi->sczOpenTagPrefix) && (NULL != pi->sczOpenTagPostfix);
    BOOL fValuePrefix = (NULL != pi->sczValuePrefix);

    hr = StrAllocString(&pi->sczPath, wzPath, 0);
    IniExitOnFailure(hr, "Failed to copy path to ini struct: %ls", wzPath);

    hr = FileToString(pi->sczPath, &sczContents, &pi->feEncoding);
    IniExitOnFailure(hr, "Failed to convert file to string: %ls", pi->sczPath);

    if (pfeEncodingFound)
    {
        *pfeEncodingFound = pi->feEncoding;
    }

    if (!sczContents || !*sczContents)
    {
        // Empty string, nothing to parse
        ExitFunction1(hr = S_OK);
    }

    dwValuePrefixLength = lstrlenW(pi->sczValuePrefix);
    hr = StrSplitAllocArray(&pi->rgsczLines, reinterpret_cast<UINT *>(&pi->cLines), sczContents, L"\n");
    IniExitOnFailure(hr, "Failed to split INI file into lines");

    for (DWORD i = 0; i < pi->cLines; ++i)
    {
        if (!*pi->rgsczLines[i] || '\r' == *pi->rgsczLines[i])
        {
            continue;
        }

        if (pi->sczCommentLinePrefix)
        {
            wzCommentLinePrefix = wcsstr(pi->rgsczLines[i], pi->sczCommentLinePrefix);

            if (wzCommentLinePrefix && wzCommentLinePrefix <= pi->rgsczLines[i] + 1)
            {
                continue;
            }
        }

        if (pi->sczOpenTagPrefix)
        {
            wzOpenTagPrefix = wcsstr(pi->rgsczLines[i], pi->sczOpenTagPrefix);
            if (wzOpenTagPrefix)
            {
                // If there is an open tag prefix but there is anything but whitespace before it, then it's NOT an open tag prefix
                // This is important, for example, to support values with names like "Array[0]=blah" in INI format
                for (wzTemp = pi->rgsczLines[i]; wzTemp < wzOpenTagPrefix; ++wzTemp)
                {
                    if (*wzTemp != L' ' && *wzTemp != L'\t')
                    {
                        wzOpenTagPrefix = NULL;
                        break;
                    }
                }
            }
        }

        if (pi->sczOpenTagPostfix)
        {
            wzOpenTagPostfix = wcsstr(pi->rgsczLines[i], pi->sczOpenTagPostfix);
        }

        wzValueNameStart = NULL;

        if (pi->sczValuePrefix)
        {
            wzValuePrefix = wcsstr(pi->rgsczLines[i], pi->sczValuePrefix);
            if (wzValuePrefix != NULL)
            {
                wzValueNameStart = wzValuePrefix + dwValuePrefixLength;
            }
        }
        else
        {
            wzValueNameStart = pi->rgsczLines[i];
        }

        if (pi->sczValueSeparator && NULL != wzValueNameStart && *wzValueNameStart != L'\0')
        {
            dwValueSeparatorExceptionLength = 0;
            for (DWORD j = 0; j < pi->cValueSeparatorExceptions; ++j)
            {
                if (pi->rgsczLines[i] == wcsstr(pi->rgsczLines[i], pi->rgsczValueSeparatorExceptions[j]))
                {
                    dwValueSeparatorExceptionLength = lstrlenW(pi->rgsczValueSeparatorExceptions[j]);
                    break;
                }
            }

            wzValueSeparator = wcsstr(wzValueNameStart + dwValueSeparatorExceptionLength, pi->sczValueSeparator);
        }

        // Don't keep the endline
        if (pi->rgsczLines[i][lstrlenW(pi->rgsczLines[i])-1] == L'\r')
        {
            pi->rgsczLines[i][lstrlenW(pi->rgsczLines[i])-1] = L'\0';
        }

        if (fSections && wzOpenTagPrefix && wzOpenTagPostfix && wzOpenTagPrefix < wzOpenTagPostfix && (NULL == wzCommentLinePrefix || wzOpenTagPrefix < wzCommentLinePrefix))
        {
            // There is an section starting here, let's keep track of it and move on
            hr = StrAllocString(&sczCurrentSection, wzOpenTagPrefix + lstrlenW(pi->sczOpenTagPrefix), wzOpenTagPostfix - (wzOpenTagPrefix + lstrlenW(pi->sczOpenTagPrefix)));
            IniExitOnFailure(hr, "Failed to record section name for line: %ls of INI file: %ls", pi->rgsczLines[i], pi->sczPath);

            // Sections will be calculated dynamically after any set operations, so don't include this in the list of lines to remember for output
            ReleaseNullStr(pi->rgsczLines[i]);
        }
        else if (wzValueSeparator && (NULL == wzCommentLinePrefix || wzValueSeparator < wzCommentLinePrefix)
            && (!fValuePrefix || wzValuePrefix))
        {
            if (fValuePrefix)
            {
                wzValueBegin = wzValuePrefix + lstrlenW(pi->sczValuePrefix);
            }
            else
            {
                wzValueBegin = pi->rgsczLines[i];
            }

            hr = MemEnsureArraySize(reinterpret_cast<void **>(&pi->rgivValues), pi->cValues + 1, sizeof(INI_VALUE), 100);
            IniExitOnFailure(hr, "Failed to increase array size for value array");

            if (sczCurrentSection)
            {
                hr = StrAllocString(&sczName, sczCurrentSection, 0);
                IniExitOnFailure(hr, "Failed to copy current section name");

                hr = StrAllocConcat(&sczName, wzSectionSeparator, 0);
                IniExitOnFailure(hr, "Failed to copy current section name");
            }

            hr = StrAllocConcat(&sczName, wzValueBegin, wzValueSeparator - wzValueBegin);
            IniExitOnFailure(hr, "Failed to copy name");

            hr = StrAllocString(&sczValue, wzValueSeparator + lstrlenW(pi->sczValueSeparator), 0);
            IniExitOnFailure(hr, "Failed to copy value");

            hr = StrTrimWhitespace(&sczNameTrimmed, sczName);
            IniExitOnFailure(hr, "Failed to trim whitespace from name");

            hr = StrTrimWhitespace(&sczValueTrimmed, sczValue);
            IniExitOnFailure(hr, "Failed to trim whitespace from value");

            pi->rgivValues[pi->cValues].wzName = const_cast<LPCWSTR>(sczNameTrimmed);
            sczNameTrimmed = NULL;
            pi->rgivValues[pi->cValues].wzValue = const_cast<LPCWSTR>(sczValueTrimmed);
            sczValueTrimmed = NULL;
            pi->rgivValues[pi->cValues].dwLineNumber = i + 1;

            ++pi->cValues;

            // Values will be calculated dynamically after any set operations, so don't include this in the list of lines to remember for output
            ReleaseNullStr(pi->rgsczLines[i]);
        }
        else
        {
            // Must be a comment, so ignore it and keep it in the list to output
        }

        ReleaseNullStr(sczName);
    }

LExit:
    ReleaseStr(sczCurrentSection);
    ReleaseStr(sczContents);
    ReleaseStr(sczName);
    ReleaseStr(sczNameTrimmed);
    ReleaseStr(sczValue);
    ReleaseStr(sczValueTrimmed);

    return hr;
}

extern "C" HRESULT DAPI IniGetValueList(
    __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
    __deref_out_ecount_opt(*pcValues) INI_VALUE** prgivValues,
    __out DWORD *pcValues
    )
{
    HRESULT hr = S_OK;

    INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);

    *prgivValues = pi->rgivValues;
    *pcValues = pi->cValues;

    return hr;
}

extern "C" HRESULT DAPI IniGetValue(
    __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
    __in LPCWSTR wzValueName,
    __deref_out_z LPWSTR* psczValue
    )
{
    HRESULT hr = S_OK;

    INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
    INI_VALUE *pValue = NULL;

    for (DWORD i = 0; i < pi->cValues; ++i)
    {
        if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pi->rgivValues[i].wzName, -1, wzValueName, -1))
        {
            pValue = pi->rgivValues + i;
            break;
        }
    }

    if (NULL == pValue)
    {
        hr = E_NOTFOUND;
        IniExitOnFailure(hr, "Failed to check for INI value: %ls", wzValueName);
    }

    if (NULL == pValue->wzValue)
    {
        ExitFunction1(hr = E_NOTFOUND);
    }

    hr = StrAllocString(psczValue, pValue->wzValue, 0);
    IniExitOnFailure(hr, "Failed to make copy of value while looking up INI value named: %ls", wzValueName);

LExit:
    return hr;
}

extern "C" HRESULT DAPI IniSetValue(
    __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
    __in LPCWSTR wzValueName,
    __in_z_opt LPCWSTR wzValue
    )
{
    HRESULT hr = S_OK;
    LPWSTR sczSectionPrefix = NULL; // includes section name and backslash
    LPWSTR sczName = NULL;
    LPWSTR sczValue = NULL;
    DWORD dwInsertIndex = DWORD_MAX;

    INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
    INI_VALUE *pValue = NULL;

    for (DWORD i = 0; i < pi->cValues; ++i)
    {
        if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pi->rgivValues[i].wzName, -1, wzValueName, -1))
        {
            pValue = pi->rgivValues + i;
            break;
        }
    }

    // We're killing the value
    if (NULL == wzValue)
    {
        if (pValue && pValue->wzValue)
        {
            pi->fModified = TRUE;
            sczValue = const_cast<LPWSTR>(pValue->wzValue);
            pValue->wzValue = NULL;
            ReleaseNullStr(sczValue);
        }

        ExitFunction();
    }
    else
    {
        if (pValue)
        {
            if (CSTR_EQUAL != ::CompareStringW(LOCALE_INVARIANT, 0, pValue->wzValue, -1, wzValue, -1))
            {
                pi->fModified = TRUE;
                hr = StrAllocString(const_cast<LPWSTR *>(&pValue->wzValue), wzValue, 0);
                IniExitOnFailure(hr, "Failed to update value INI value named: %ls", wzValueName);
            }

            ExitFunction1(hr = S_OK);
        }
        else
        {
            if (wzValueName)
            {
                hr = GetSectionPrefixFromName(wzValueName, &sczSectionPrefix);
                IniExitOnFailure(hr, "Failed to get section prefix from value name: %ls", wzValueName);
            }

            // If we have a section prefix, figure out the index to insert it (at the end of the section it belongs in)
            if (sczSectionPrefix)
            {
                for (DWORD i = 0; i < pi->cValues; ++i)
                {
                    if (0 == wcsncmp(pi->rgivValues[i].wzName, sczSectionPrefix, lstrlenW(sczSectionPrefix)))
                    {
                        dwInsertIndex = i;
                    }
                    else if (DWORD_MAX != dwInsertIndex)
                    {
                        break;
                    }
                }
            }
            else
            {
                for (DWORD i = 0; i < pi->cValues; ++i)
                {
                    if (NULL == wcsstr(pi->rgivValues[i].wzName, wzSectionSeparator))
                    {
                        dwInsertIndex = i;
                    }
                    else if (DWORD_MAX != dwInsertIndex)
                    {
                        break;
                    }
                }
            }

            // Otherwise, just add it to the end
            if (DWORD_MAX == dwInsertIndex)
            {
                dwInsertIndex = pi->cValues;
            }

            pi->fModified = TRUE;
            hr = MemInsertIntoArray(reinterpret_cast<void **>(&pi->rgivValues), dwInsertIndex, 1, pi->cValues + 1, sizeof(INI_VALUE), 100);
            IniExitOnFailure(hr, "Failed to insert value into array");

            hr = StrAllocString(&sczName, wzValueName, 0);
            IniExitOnFailure(hr, "Failed to copy name");

            hr = StrAllocString(&sczValue, wzValue, 0);
            IniExitOnFailure(hr, "Failed to copy value");

            pi->rgivValues[dwInsertIndex].wzName = const_cast<LPCWSTR>(sczName);
            sczName = NULL;
            pi->rgivValues[dwInsertIndex].wzValue = const_cast<LPCWSTR>(sczValue);
            sczValue = NULL;

            ++pi->cValues;
        }
    }

LExit:
    ReleaseStr(sczName);
    ReleaseStr(sczValue);

    return hr;
}

extern "C" HRESULT DAPI IniWriteFile(
    __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
    __in_z_opt LPCWSTR wzPath,
    __in FILE_ENCODING feOverrideEncoding
    )
{
    HRESULT hr = S_OK;
    LPWSTR sczCurrentSectionPrefix = NULL;
    LPWSTR sczNewSectionPrefix = NULL;
    LPWSTR sczContents = NULL;
    LPCWSTR wzName = NULL;
    DWORD dwLineArrayIndex = 1;
    FILE_ENCODING feEncoding;

    INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);

    if (FILE_ENCODING_UNSPECIFIED == feOverrideEncoding)
    {
        feEncoding = pi->feEncoding;
    }
    else
    {
        feEncoding = feOverrideEncoding;
    }

    if (FILE_ENCODING_UNSPECIFIED == feEncoding)
    {
        feEncoding = FILE_ENCODING_UTF16_WITH_BOM;
    }

    if (!pi->fModified)
    {
        ExitFunction1(hr = S_OK);
    }
    if (NULL == wzPath && NULL == pi->sczPath)
    {
        ExitFunction1(hr = E_NOTFOUND);
    }

    BOOL fSections = (pi->sczOpenTagPrefix) && (pi->sczOpenTagPostfix);

    hr = StrAllocString(&sczContents, L"", 0);
    IniExitOnFailure(hr, "Failed to begin contents string as empty string");

    // Insert any beginning lines we didn't understand like comments
    if (0 < pi->cLines)
    {
        while (pi->rgsczLines[dwLineArrayIndex])
        {
            hr = StrAllocConcat(&sczContents, pi->rgsczLines[dwLineArrayIndex], 0);
            IniExitOnFailure(hr, "Failed to add previous line to ini output buffer in-memory");

            hr = StrAllocConcat(&sczContents, L"\r\n", 2);
            IniExitOnFailure(hr, "Failed to add endline to ini output buffer in-memory");

            ++dwLineArrayIndex;
        }
    }

    for (DWORD i = 0; i < pi->cValues; ++i)
    {
        // Skip if this value was killed off
        if (NULL == pi->rgivValues[i].wzValue)
        {
            continue;
        }

        // Now generate any lines for the current value like value line and maybe also a new section line before it

        // First see if we need to write a section line
        hr = GetSectionPrefixFromName(pi->rgivValues[i].wzName, &sczNewSectionPrefix);
        IniExitOnFailure(hr, "Failed to get section prefix from name: %ls", pi->rgivValues[i].wzName);

        // If the new section prefix is different, write a section out for it
        if (fSections && sczNewSectionPrefix && (NULL == sczCurrentSectionPrefix || CSTR_EQUAL != ::CompareStringW(LOCALE_INVARIANT, 0, sczNewSectionPrefix, -1, sczCurrentSectionPrefix, -1)))
        {
            hr = StrAllocConcat(&sczContents, pi->sczOpenTagPrefix, 0);
            IniExitOnFailure(hr, "Failed to concat open tag prefix to string");

            // Exclude section separator (i.e. backslash) from new section prefix
            hr = StrAllocConcat(&sczContents, sczNewSectionPrefix, lstrlenW(sczNewSectionPrefix)-lstrlenW(wzSectionSeparator));
            IniExitOnFailure(hr, "Failed to concat section name to string");

            hr = StrAllocConcat(&sczContents, pi->sczOpenTagPostfix, 0);
            IniExitOnFailure(hr, "Failed to concat open tag postfix to string");

            hr = StrAllocConcat(&sczContents, L"\r\n", 2);
            IniExitOnFailure(hr, "Failed to add endline to ini output buffer in-memory");
            
            ReleaseNullStr(sczCurrentSectionPrefix);
            sczCurrentSectionPrefix = sczNewSectionPrefix;
            sczNewSectionPrefix = NULL;
        }

        // Inserting lines we read before the current value if appropriate
        while (pi->rgivValues[i].dwLineNumber > dwLineArrayIndex)
        {
            // Skip any lines were purposely forgot
            if (NULL == pi->rgsczLines[dwLineArrayIndex])
            {
                ++dwLineArrayIndex;
                continue;
            }

            hr = StrAllocConcat(&sczContents, pi->rgsczLines[dwLineArrayIndex++], 0);
            IniExitOnFailure(hr, "Failed to add previous line to ini output buffer in-memory");

            hr = StrAllocConcat(&sczContents, L"\r\n", 2);
            IniExitOnFailure(hr, "Failed to add endline to ini output buffer in-memory");
        }

        wzName = pi->rgivValues[i].wzName;
        if (fSections)
        {
            wzName += lstrlenW(sczCurrentSectionPrefix);
        }

        // OK, now just write the name/value pair, if it isn't deleted
        if (pi->sczValuePrefix)
        {
            hr = StrAllocConcat(&sczContents, pi->sczValuePrefix, 0);
            IniExitOnFailure(hr, "Failed to concat value prefix to ini output buffer");
        }

        hr = StrAllocConcat(&sczContents, wzName, 0);
        IniExitOnFailure(hr, "Failed to concat value name to ini output buffer");

        hr = StrAllocConcat(&sczContents, pi->sczValueSeparator, 0);
        IniExitOnFailure(hr, "Failed to concat value separator to ini output buffer");

        hr = StrAllocConcat(&sczContents, pi->rgivValues[i].wzValue, 0);
        IniExitOnFailure(hr, "Failed to concat value to ini output buffer");

        hr = StrAllocConcat(&sczContents, L"\r\n", 2);
        IniExitOnFailure(hr, "Failed to add endline to ini output buffer in-memory");
    }

    // If no path was specified, use the path to the file we parsed
    if (NULL == wzPath)
    {
        wzPath = pi->sczPath;
    }

    hr = FileFromString(wzPath, 0, sczContents, feEncoding);
    IniExitOnFailure(hr, "Failed to write INI contents out to file: %ls", wzPath);

LExit:
    ReleaseStr(sczContents);
    ReleaseStr(sczCurrentSectionPrefix);
    ReleaseStr(sczNewSectionPrefix);

    return hr;
}

static void UninitializeIniValue(
    INI_VALUE *pivValue
    )
{
    ReleaseStr(const_cast<LPWSTR>(pivValue->wzName));
    ReleaseStr(const_cast<LPWSTR>(pivValue->wzValue));
}

static HRESULT GetSectionPrefixFromName(
    __in_z LPCWSTR wzName,
    __deref_inout_z LPWSTR* psczOutput
    )
{
    HRESULT hr = S_OK;
    LPCWSTR wzSectionDelimiter = NULL;

    ReleaseNullStr(*psczOutput);

    wzSectionDelimiter = wcsstr(wzName, wzSectionSeparator);
    if (wzSectionDelimiter && wzSectionDelimiter != wzName)
    {
        hr = StrAllocString(psczOutput, wzName, wzSectionDelimiter - wzName + 1);
        IniExitOnFailure(hr, "Failed to copy section prefix");
    }

LExit:
    return hr;
}