/*
 * Win32 5.1 uxtheme ini file processing
 *
 * Copyright (C) 2004 Kevin Koltzau
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>

#include "windef.h"
#include "winbase.h"
#include "winnls.h"

#include "wine/debug.h"
#include "wine/heap.h"

WINE_DEFAULT_DEBUG_CHANNEL(uxtheme);

/***********************************************************************
 * Defines and global variables
 */

typedef struct _UXINI_FILE {
    LPCWSTR lpIni;
    LPCWSTR lpCurLoc;
    LPCWSTR lpEnd;
} UXINI_FILE, *PUXINI_FILE;

/***********************************************************************/

/**********************************************************************
 *      UXINI_LoadINI
 *
 * Load a theme INI file out of resources from the specified
 * theme
 *
 * PARAMS
 *     tf                  Theme to load INI file out of resources
 *     lpName              Resource name of the INI file
 *
 * RETURNS
 *     INI file, or NULL if not found
 */
PUXINI_FILE UXINI_LoadINI(HMODULE hTheme, LPCWSTR lpName) {
    HRSRC hrsc;
    LPCWSTR lpThemesIni = NULL;
    PUXINI_FILE uf;
    DWORD dwIniSize;

    TRACE("Loading resource INI %s\n", debugstr_w(lpName));

    if((hrsc = FindResourceW(hTheme, lpName, L"TEXTFILE"))) {
        if(!(lpThemesIni = LoadResource(hTheme, hrsc))) {
            TRACE("%s resource not found\n", debugstr_w(lpName));
            return NULL;
        }
    }

    dwIniSize = SizeofResource(hTheme, hrsc) / sizeof(WCHAR);
    uf = heap_alloc(sizeof(*uf));
    uf->lpIni = lpThemesIni;
    uf->lpCurLoc = lpThemesIni;
    uf->lpEnd = lpThemesIni + dwIniSize;
    return uf;
}

/**********************************************************************
 *      UXINI_CloseINI
 *
 * Close an open theme INI file
 *
 * PARAMS
 *     uf                  Theme INI file to close
 */
void UXINI_CloseINI(PUXINI_FILE uf)
{
    heap_free(uf);
}

/**********************************************************************
 *      UXINI_eof
 *
 * Determines if we are at the end of the INI file
 *
 * PARAMS
 *     uf                  Theme INI file to test
 */
static inline BOOL UXINI_eof(PUXINI_FILE uf)
{
    return uf->lpCurLoc >= uf->lpEnd;
}

/**********************************************************************
 *      UXINI_isspace
 *
 * Check if a character is a space character
 *
 * PARAMS
 *     c                   Character to test
 */
static inline BOOL UXINI_isspace(WCHAR c)
{
    if (isspace(c)) return TRUE;
    if (c=='\r') return TRUE;
    return FALSE;
}

/**********************************************************************
 *      UXINI_GetNextLine
 *
 * Get the next line in the INI file, non NULL terminated
 * removes whitespace at beginning and end of line, and removes comments
 *
 * PARAMS
 *     uf                  INI file to retrieve next line
 *     dwLen               Location to store pointer to line length
 *
 * RETURNS
 *     The section name, non NULL terminated
 */
static LPCWSTR UXINI_GetNextLine(PUXINI_FILE uf, DWORD *dwLen)
{
    LPCWSTR lpLineEnd;
    LPCWSTR lpLineStart;
    DWORD len;
    do {
        if(UXINI_eof(uf)) return NULL;
        /* Skip whitespace and empty lines */
        while(!UXINI_eof(uf) && (UXINI_isspace(*uf->lpCurLoc) || *uf->lpCurLoc == '\n')) uf->lpCurLoc++;
        lpLineStart = uf->lpCurLoc;
        lpLineEnd = uf->lpCurLoc;
        while(!UXINI_eof(uf) && *uf->lpCurLoc != '\n' && *uf->lpCurLoc != ';') lpLineEnd = ++uf->lpCurLoc;
        /* If comment was found, skip the rest of the line */
        if(*uf->lpCurLoc == ';')
            while(!UXINI_eof(uf) && *uf->lpCurLoc != '\n') uf->lpCurLoc++;
        len = (lpLineEnd - lpLineStart);
        if(*lpLineStart != ';' && len == 0)
            return NULL;
    } while(*lpLineStart == ';');
    /* Remove whitespace from end of line */
    while(UXINI_isspace(lpLineStart[len-1])) len--;
    *dwLen = len;

    return lpLineStart;
}

static inline void UXINI_UnGetToLine(PUXINI_FILE uf, LPCWSTR lpLine)
{
    uf->lpCurLoc = lpLine;
}

/**********************************************************************
 *      UXINI_GetNextSection
 *
 * Locate the next section in the ini file, and return pointer to
 * section name, non NULL terminated. Use dwLen to determine length
 *
 * PARAMS
 *     uf                  INI file to search, search starts at current location
 *     dwLen               Location to store pointer to section name length
 *
 * RETURNS
 *     The section name, non NULL terminated
 */
LPCWSTR UXINI_GetNextSection(PUXINI_FILE uf, DWORD *dwLen)
{
    LPCWSTR lpLine;
    while((lpLine = UXINI_GetNextLine(uf, dwLen))) {
        /* Assuming a ']' ending to the section name */
        if(lpLine[0] == '[') {
            lpLine++;
            *dwLen -= 2;
            break;
        }
    }
    return lpLine;
}

/**********************************************************************
 *      UXINI_FindSection
 *
 * Locate a section with the specified name, search starts
 * at current location in ini file
 *
 * PARAMS
 *     uf                  INI file to search, search starts at current location
 *     lpName              Name of the section to locate
 *
 * RETURNS
 *     TRUE if section was found, FALSE otherwise
 */
BOOL UXINI_FindSection(PUXINI_FILE uf, LPCWSTR lpName)
{
    LPCWSTR lpSection;
    DWORD dwLen;
    while((lpSection = UXINI_GetNextSection(uf, &dwLen))) {
        if(CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, lpSection, dwLen, lpName, -1) == CSTR_EQUAL) {
            return TRUE;
        }
    }
    return FALSE;
}

/**********************************************************************
 *      UXINI_GetNextValue
 *
 * Locate the next value in the current section
 *
 * PARAMS
 *     uf                  INI file to search, search starts at current location
 *     dwNameLen            Location to store pointer to value name length
 *     lpValue              Location to store pointer to the value
 *     dwValueLen           Location to store pointer to value length
 *
 * RETURNS
 *     The value name, non NULL terminated
 */
LPCWSTR UXINI_GetNextValue(PUXINI_FILE uf, DWORD *dwNameLen, LPCWSTR *lpValue, DWORD *dwValueLen)
{
    LPCWSTR lpLine;
    LPCWSTR lpLineEnd;
    LPCWSTR name = NULL;
    LPCWSTR value = NULL;
    DWORD vallen = 0;
    DWORD namelen = 0;
    DWORD dwLen;
    lpLine = UXINI_GetNextLine(uf, &dwLen);
    if(!lpLine)
        return NULL;
    if(lpLine[0] == '[') {
        UXINI_UnGetToLine(uf, lpLine);
        return NULL;
    }
    lpLineEnd = lpLine + dwLen;

    name = lpLine;
    while(namelen < dwLen && *lpLine != '=') {
        lpLine++;
        namelen++;
    }
    if(*lpLine != '=') return NULL;
    lpLine++;

    /* Remove whitespace from end of name */
    while(UXINI_isspace(name[namelen-1])) namelen--;
    /* Remove whitespace from beginning of value */
    while(UXINI_isspace(*lpLine) && lpLine < lpLineEnd) lpLine++;
    value = lpLine;
    vallen = dwLen-(value-name);

    *dwNameLen = namelen;
    *dwValueLen = vallen;
    *lpValue = value;

    return name;
}

/**********************************************************************
 *      UXINI_FindValue
 *
 * Locate a value by name
 *
 * PARAMS
 *     uf                   INI file to search, search starts at current location
 *     lpName               Value name to locate
 *     lpValue              Location to store pointer to the value
 *     dwValueLen           Location to store pointer to value length
 *
 * RETURNS
 *     The value name, non NULL terminated
 */
BOOL UXINI_FindValue(PUXINI_FILE uf, LPCWSTR lpName, LPCWSTR *lpValue, DWORD *dwValueLen)
{
    LPCWSTR name;
    DWORD namelen;

    while((name = UXINI_GetNextValue(uf, &namelen, lpValue, dwValueLen))) {
        if(CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, name, namelen, lpName, -1) == CSTR_EQUAL) {
            return TRUE;
        }
    }
    return FALSE;
}
