/*
 *  FreeLoader
 *  Copyright (C) 1998-2003  Brian Palmer  <brianp@sginet.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <freeldr.h>
#include <debug.h>

DBG_DEFAULT_CHANNEL(INIFILE);

LIST_ENTRY        IniFileSectionListHead;
BOOLEAN            IniFileSectionInitialized = FALSE;
ULONG                    IniFileSectionCount = 0;
ULONG                    IniFileSettingCount = 0;


BOOLEAN IniParseFile(PCHAR IniFileData, ULONG IniFileSize)
{
    ULONG                    CurrentOffset;
    ULONG                    CurrentLineNumber;
    PCHAR                IniFileLine;
    ULONG                    IniFileLineSize;
    ULONG                    LineLength;
    PINI_SECTION        CurrentSection = NULL;
    PINI_SECTION_ITEM    CurrentItem = NULL;

    TRACE("IniParseFile() IniFileSize: %d\n", IniFileSize);

    if (!IniFileSectionInitialized)
    {
        InitializeListHead(&IniFileSectionListHead);
        IniFileSectionInitialized = TRUE;
    }

    // Start with an 80-byte buffer
    IniFileLineSize = 80;
    IniFileLine = MmHeapAlloc(IniFileLineSize);
    if (!IniFileLine)
    {
        return FALSE;
    }

    // Loop through each line and parse it
    CurrentLineNumber = 0;
    CurrentOffset = 0;
    while (CurrentOffset < IniFileSize)
    {
        // First check the line size and increase our buffer if necessary
        if (IniFileLineSize < IniGetNextLineSize(IniFileData, IniFileSize, CurrentOffset))
        {
            IniFileLineSize = IniGetNextLineSize(IniFileData, IniFileSize, CurrentOffset);
            MmHeapFree(IniFileLine);
            IniFileLine = MmHeapAlloc(IniFileLineSize);
            if (!IniFileLine)
            {
                return FALSE;
            }
        }

        // Get the line of data
        CurrentOffset = IniGetNextLine(IniFileData, IniFileSize, IniFileLine, IniFileLineSize, CurrentOffset);
        LineLength = (ULONG)strlen(IniFileLine);

        // If it is a blank line or a comment then skip it
        if (IniIsLineEmpty(IniFileLine, LineLength) || IniIsCommentLine(IniFileLine, LineLength))
        {
            CurrentLineNumber++;
            continue;
        }

        // Check if it is a new section
        if (IniIsSectionName(IniFileLine, LineLength))
        {
            // Allocate a new section structure
            CurrentSection = MmHeapAlloc(sizeof(INI_SECTION));
            if (!CurrentSection)
            {
                MmHeapFree(IniFileLine);
                return FALSE;
            }

            RtlZeroMemory(CurrentSection, sizeof(INI_SECTION));

            // Allocate the section name buffer
            CurrentSection->SectionName = MmHeapAlloc(IniGetSectionNameSize(IniFileLine, LineLength));
            if (!CurrentSection->SectionName)
            {
                MmHeapFree(CurrentSection);
                MmHeapFree(IniFileLine);
                return FALSE;
            }

            // Get the section name
            IniExtractSectionName(CurrentSection->SectionName, IniFileLine, LineLength);
            InitializeListHead(&CurrentSection->SectionItemList);

            // Add it to the section list head
            IniFileSectionCount++;
            InsertTailList(&IniFileSectionListHead, &CurrentSection->ListEntry);

            CurrentLineNumber++;
            continue;
        }

        // Check if it is a setting
        if (IniIsSetting(IniFileLine, LineLength))
        {
            // First check to make sure we're inside a [section]
            if (CurrentSection == NULL)
            {
                printf("Error: freeldr.ini:%lu: Setting '%s' found outside of a [section].\n", CurrentLineNumber, IniFileLine);
                printf("Press any key to continue...\n");
                MachConsGetCh();
                CurrentLineNumber++;
                continue;
            }

            // Allocate a new item structure
            CurrentItem = MmHeapAlloc(sizeof(INI_SECTION_ITEM));
            if (!CurrentItem)
            {
                MmHeapFree(IniFileLine);
                return FALSE;
            }

            RtlZeroMemory(CurrentItem, sizeof(INI_SECTION_ITEM));

            // Allocate the setting name buffer
            CurrentItem->ItemName = MmHeapAlloc(IniGetSettingNameSize(IniFileLine, LineLength));
            if (!CurrentItem->ItemName)
            {
                MmHeapFree(CurrentItem);
                MmHeapFree(IniFileLine);
                return FALSE;
            }

            // Allocate the setting value buffer
            CurrentItem->ItemValue = MmHeapAlloc(IniGetSettingValueSize(IniFileLine, LineLength));
            if (!CurrentItem->ItemValue)
            {
                MmHeapFree(CurrentItem->ItemName);
                MmHeapFree(CurrentItem);
                MmHeapFree(IniFileLine);
                return FALSE;
            }

            // Get the section name
            IniExtractSettingName(CurrentItem->ItemName, IniFileLine, LineLength);
            IniExtractSettingValue(CurrentItem->ItemValue, IniFileLine, LineLength);

            // Add it to the current section
            IniFileSettingCount++;
            CurrentSection->SectionItemCount++;
            InsertTailList(&CurrentSection->SectionItemList, &CurrentItem->ListEntry);

            CurrentLineNumber++;
            continue;
        }

        CurrentLineNumber++;
    }

    TRACE("Parsed %d sections and %d settings.\n", IniFileSectionCount, IniFileSettingCount);
    TRACE("IniParseFile() done.\n");

    return TRUE;
}

ULONG IniGetNextLineSize(PCHAR IniFileData, ULONG IniFileSize, ULONG CurrentOffset)
{
    ULONG        LineCharCount = 0;

    // Loop through counting chars until we hit the end of the
    // file or we encounter a new line char
    for (; (CurrentOffset < IniFileSize); CurrentOffset++)
    {
        // Increment the line character count
        LineCharCount++;

        // Check for new line char
        if (IniFileData[CurrentOffset] == '\n')
        {
            break;
        }
    }

    // Add one for the NULL-terminator
    LineCharCount++;

    // Send back line character count
    return LineCharCount;
}

ULONG IniGetNextLine(PCHAR IniFileData, ULONG IniFileSize, PCHAR Buffer, ULONG BufferSize, ULONG CurrentOffset)
{
    ULONG        Idx;

    // Loop through grabbing chars until we hit the end of the
    // file or we encounter a new line char
    for (Idx=0; (CurrentOffset < IniFileSize); CurrentOffset++)
    {
        // If we haven't exceeded our buffer size yet
        // then store another char
        if (Idx < (BufferSize - 1))
        {
            Buffer[Idx++] = IniFileData[CurrentOffset];
        }

        // Check for new line char
        if (IniFileData[CurrentOffset] == '\n')
        {
            CurrentOffset++;
            break;
        }
    }

    // Terminate the string
    Buffer[Idx] = '\0';

    // Get rid of newline & linefeed characters (if any)
    while(Idx && (Buffer[--Idx] == '\n' || Buffer[Idx] == '\r'))
        Buffer[Idx] = '\0';

    // Send back new offset
    return CurrentOffset;
}

BOOLEAN IniIsLineEmpty(PCHAR LineOfText, ULONG TextLength)
{
    ULONG        Idx;

    // Check for text (skipping whitespace)
    for (Idx=0; Idx<TextLength; Idx++)
    {
        if ((LineOfText[Idx] == ' ') ||
            (LineOfText[Idx] == '\t') ||
            (LineOfText[Idx] == '\n') ||
            (LineOfText[Idx] == '\r'))
        {
            continue;
        }
        else
        {
            return FALSE;
        }
    }

    return TRUE;
}

BOOLEAN IniIsCommentLine(PCHAR LineOfText, ULONG TextLength)
{
    ULONG        Idx;

    // Check the first character (skipping whitespace)
    // and make sure that it is an opening bracket
    for (Idx=0; Idx<TextLength; Idx++)
    {
        if ((LineOfText[Idx] == ' ') ||
            (LineOfText[Idx] == '\t'))
        {
            continue;
        }
        else if (LineOfText[Idx] == INI_FILE_COMMENT_CHAR)
        {
            return TRUE;
        }
        else
        {
            break;
        }
    }

    return FALSE;
}

BOOLEAN IniIsSectionName(PCHAR LineOfText, ULONG TextLength)
{
    ULONG        Idx;

    // Check the first character (skipping whitespace)
    // and make sure that it is an opening bracket
    for (Idx=0; Idx<TextLength; Idx++)
    {
        if ((LineOfText[Idx] == ' ') ||
            (LineOfText[Idx] == '\t'))
        {
            continue;
        }
        else if (LineOfText[Idx] == '[')
        {
            return TRUE;
        }
        else
        {
            break;
        }
    }

    return FALSE;
}

ULONG IniGetSectionNameSize(PCHAR SectionNameLine, ULONG LineLength)
{
    ULONG        Idx;
    ULONG        NameSize;

    // Find the opening bracket (skipping whitespace)
    for (Idx=0; Idx<LineLength; Idx++)
    {
        if ((SectionNameLine[Idx] == ' ') ||
            (SectionNameLine[Idx] == '\t'))
        {
            continue;
        }
        else //if (SectionNameLine[Idx] == '[')
        {
            break;
        }
    }

    // Skip past the opening bracket
    Idx++;

    // Count the characters up until the closing bracket or EOL
    for (NameSize=0; Idx<LineLength; Idx++)
    {
        if ((SectionNameLine[Idx] == ']') ||
            (SectionNameLine[Idx] == '\0'))
        {
            break;
        }

        // Increment the count
        NameSize++;
    }

    // Add one for the NULL-terminator
    NameSize++;

    return NameSize;
}

VOID IniExtractSectionName(PCHAR SectionName, PCHAR SectionNameLine, ULONG LineLength)
{
    ULONG        Idx;
    ULONG        DestIdx;

    // Find the opening bracket (skipping whitespace)
    for (Idx=0; Idx<LineLength; Idx++)
    {
        if ((SectionNameLine[Idx] == ' ') ||
            (SectionNameLine[Idx] == '\t'))
        {
            continue;
        }
        else //if (SectionNameLine[Idx] == '[')
        {
            break;
        }
    }

    // Skip past the opening bracket
    Idx++;

    // Count the characters up until the closing bracket or EOL
    for (DestIdx=0; Idx<LineLength; Idx++)
    {
        if ((SectionNameLine[Idx] == ']') ||
            (SectionNameLine[Idx] == '\0'))
        {
            break;
        }

        // Grab a character and increment DestIdx
        SectionName[DestIdx] = SectionNameLine[Idx];
        DestIdx++;
    }

    // Terminate the string
    SectionName[DestIdx] = '\0';
}

BOOLEAN IniIsSetting(PCHAR LineOfText, ULONG TextLength)
{
    ULONG        Idx;

    // Basically just check for an '=' equals sign
    for (Idx=0; Idx<TextLength; Idx++)
    {
        if (LineOfText[Idx] == '=')
        {
            return TRUE;
        }
    }

    return FALSE;
}

ULONG IniGetSettingNameSize(PCHAR SettingNameLine, ULONG LineLength)
{
    ULONG        Idx;
    ULONG        NameSize;

    // Skip whitespace
    for (Idx=0; Idx<LineLength; Idx++)
    {
        if ((SettingNameLine[Idx] == ' ') ||
            (SettingNameLine[Idx] == '\t'))
        {
            continue;
        }
        else
        {
            break;
        }
    }

    // Count the characters up until the '=' equals sign or EOL
    for (NameSize=0; Idx<LineLength; Idx++)
    {
        if ((SettingNameLine[Idx] == '=') ||
            (SettingNameLine[Idx] == '\0'))
        {
            break;
        }

        // Increment the count
        NameSize++;
    }

    // Add one for the NULL-terminator
    NameSize++;

    return NameSize;
}

ULONG IniGetSettingValueSize(PCHAR SettingValueLine, ULONG LineLength)
{
    ULONG        Idx;
    ULONG        ValueSize;

    // Skip whitespace
    for (Idx=0; Idx<LineLength; Idx++)
    {
        if ((SettingValueLine[Idx] == ' ') ||
            (SettingValueLine[Idx] == '\t'))
        {
            continue;
        }
        else
        {
            break;
        }
    }

    // Skip the characters up until the '=' equals sign or EOL
    for (; Idx<LineLength; Idx++)
    {
        if (SettingValueLine[Idx] == '=')
        {
            Idx++;
            break;
        }

        // If we hit EOL then obviously the value size is zero
        if (SettingValueLine[Idx] == '\0')
        {
            return 0;
        }
    }

    // Count the characters up until the EOL
    for (ValueSize=0; Idx<LineLength; Idx++)
    {
        if (SettingValueLine[Idx] == '\0')
        {
            break;
        }

        // Increment the count
        ValueSize++;
    }

    // Add one for the NULL-terminator
    ValueSize++;

    return ValueSize;
}

VOID IniExtractSettingName(PCHAR SettingName, PCHAR SettingNameLine, ULONG LineLength)
{
    ULONG        Idx;
    ULONG        DestIdx;

    // Skip whitespace
    for (Idx=0; Idx<LineLength; Idx++)
    {
        if ((SettingNameLine[Idx] == ' ') ||
            (SettingNameLine[Idx] == '\t'))
        {
            continue;
        }
        else
        {
            break;
        }
    }

    // Get the characters up until the '=' equals sign or EOL
    for (DestIdx=0; Idx<LineLength; Idx++)
    {
        if ((SettingNameLine[Idx] == '=') ||
            (SettingNameLine[Idx] == '\0'))
        {
            break;
        }

        // Grab a character and increment DestIdx
        SettingName[DestIdx] = SettingNameLine[Idx];
        DestIdx++;
    }

    // Terminate the string
    SettingName[DestIdx] = '\0';
}

VOID IniExtractSettingValue(PCHAR SettingValue, PCHAR SettingValueLine, ULONG LineLength)
{
    ULONG        Idx;
    ULONG        DestIdx;

    // Skip whitespace
    for (Idx=0; Idx<LineLength; Idx++)
    {
        if ((SettingValueLine[Idx] == ' ') ||
            (SettingValueLine[Idx] == '\t'))
        {
            continue;
        }
        else
        {
            break;
        }
    }

    // Skip the characters up until the '=' equals sign or EOL
    for (; Idx<LineLength; Idx++)
    {
        if (SettingValueLine[Idx] == '=')
        {
            Idx++;
            break;
        }

        // If we hit EOL then obviously the value size is zero
        if (SettingValueLine[Idx] == '\0')
        {
            SettingValue[0] = '\0';
            return;
        }
    }

    // Get the characters up until the EOL
    for (DestIdx=0; Idx<LineLength; Idx++)
    {
        if (SettingValueLine[Idx] == '\0')
        {
            break;
        }

        // Grab a character and increment DestIdx
        SettingValue[DestIdx] = SettingValueLine[Idx];
        DestIdx++;
    }

    // Terminate the string
    SettingValue[DestIdx] = '\0';
}
