/*++

   Copyright    (c) 1998-2000    Microsoft Corporation

   Module  Name :
       IniFile.cpp

   Abstract:
       Test harness for LKRhash

   Author:
       George V. Reilly      (GeorgeRe)     06-Jan-1998

   Environment:
       Win32 - User Mode

   Project:
       Internet Information Server RunTime Library

   Revision History:

--*/

#include "precomp.hxx"
#include "WordHash.h"
#include "IniFile.h"


enum INI_TYPE {
    INI_WORD = 1,
    INI_DWORD,
    INI_DOUBLE,
    INI_STRING,
    INI_BOOL,
};

typedef struct _ParseOptions {
    int         m_nFieldOffset;
    const char* m_pszName;
    unsigned    m_cchName;
    INI_TYPE    m_type;
    DWORD_PTR   m_default;
} ParseOptions;

#define INI_ENTRY(_member, _name, _type, _default)  \
    {                                               \
        FIELD_OFFSET(CIniFileSettings, _member),    \
        _name,                                      \
        sizeof(_name)-1,                            \
        _type,                                      \
        (DWORD_PTR) _default,                       \
    }                                               \

static const ParseOptions g_po[] = {
    INI_ENTRY(m_tszDataFile,  "DataFile",       INI_STRING, _TEXT("??")),
    INI_ENTRY(m_nMaxKeys,     "MaxKeys",        INI_DWORD,  MAXKEYS),
    INI_ENTRY(m_dblHighLoad,  "MaxLoadFactor",  INI_DOUBLE, LK_DFLT_MAXLOAD),
    INI_ENTRY(m_nInitSize,    "InitSize",       INI_DWORD,  LK_DFLT_INITSIZE),
    INI_ENTRY(m_nSubTables,   "NumSubTables",   INI_DWORD,LK_DFLT_NUM_SUBTBLS),
    INI_ENTRY(m_nLookupFreq,  "LookupFrequency",INI_DWORD,  5),
    INI_ENTRY(m_nMinThreads,  "MinThreads",     INI_DWORD,  1),
    INI_ENTRY(m_nMaxThreads,  "MaxThreads",     INI_DWORD,  4),
    INI_ENTRY(m_nRounds,      "NumRounds",      INI_DWORD,  1),
    INI_ENTRY(m_nSeed,        "RandomSeed",     INI_DWORD,  1234),
    INI_ENTRY(m_fCaseInsensitive,"CaseInsensitive",INI_BOOL, FALSE),
    INI_ENTRY(m_fMemCmp,      "MemCmp",         INI_BOOL,   FALSE),
    INI_ENTRY(m_nLastChars,   "NumLastChars",   INI_DWORD,  0),
    INI_ENTRY(m_wTableSpin,   "TableLockSpinCount",INI_WORD, LOCK_DEFAULT_SPINS),
    INI_ENTRY(m_wBucketSpin,  "BucketLockSpinCount",INI_WORD,LOCK_DEFAULT_SPINS),
    INI_ENTRY(m_dblSpinAdjFctr,"SpinAdjustmentFactor",INI_DOUBLE, 1),
    INI_ENTRY(m_fTestIterators,"TestIterators", INI_BOOL,   FALSE),
    INI_ENTRY(m_nInsertIfNotFound, "InsertIfNotFound",INI_DWORD, 0),
    INI_ENTRY(m_nFindKeyCopy, "FindKeyCopy",    INI_DWORD,  0),
    INI_ENTRY(m_fNonPagedAllocs,"NonPagedAllocs", INI_BOOL, TRUE),
    INI_ENTRY(m_fRefTrace,     "RefTrace",      INI_BOOL,   FALSE),
    {-1}  // last entry
};


void
CIniFileSettings::Dump(
    LPCTSTR ptszProlog,
    LPCTSTR ptszEpilog) const
{
    TCHAR tsz[50];

    _tprintf(_TEXT("%s\n"), ptszProlog);
    _tprintf(_TEXT("IniFile=\"%s\"\n"),
             m_tszIniFile);
    _tprintf(_TEXT("DataFile=\"%s\". %s keys.\n"),
             m_tszDataFile, CommaNumber(m_nMaxKeys, tsz));
    _tprintf(_TEXT("Max load = %.1f, initsize = %d, %d subtables.\n"),
             m_dblHighLoad, m_nInitSize, m_nSubTables);
    _tprintf(_TEXT("Lookup freq = %d, %d-%d threads, %d round%s.\n"),
             m_nLookupFreq, m_nMinThreads, m_nMaxThreads,
             m_nRounds, (m_nRounds==1 ? "" : "s"));
    _tprintf(_TEXT("Seed=%d, CaseInsensitive=%d, MemCmp=%d, LastChars=%d\n"),
             m_nSeed, m_fCaseInsensitive, m_fMemCmp, m_nLastChars);
    _tprintf(_TEXT("Spin Count: Table = %hd, Bucket = %hd, AdjFactor=%.1f\n"),
             m_wTableSpin, m_wBucketSpin, m_dblSpinAdjFctr);
    _tprintf(_TEXT("TestIterators=%d, InsertIfNotFound=%d, FindKeyCopy=%d\n"),
             m_fTestIterators, m_nInsertIfNotFound, m_nFindKeyCopy);
    _tprintf(_TEXT("NonPagedAllocs=%d, RefTrace=%d\n"),
             m_fNonPagedAllocs, m_fRefTrace);
    _tprintf(_TEXT("%s\n"), ptszEpilog);
}

#if defined(IRTLDEBUG)
# define DUMP_INIFILE(pifs, Pro, Epi)   pifs->Dump(Pro, Epi)
#else
# define DUMP_INIFILE(pifs, Pro, Epi)   ((void) 0)
#endif


DWORD
ReadFileIntoBuffer(
	LPCTSTR ptszFile,
    PBYTE   pbBuffer,
    DWORD   cbBuffer)
{
#ifndef LKRHASH_KERNEL_MODE
    HANDLE hFile =
        CreateFile(
            ptszFile,
            GENERIC_READ,
            0,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL);

    if (hFile == INVALID_HANDLE_VALUE)
        return 0;

    DWORD cbRead = 0, cbFileSizeLow, cbFileSizeHigh;

    cbFileSizeLow = GetFileSize(hFile, &cbFileSizeHigh);

    bool fBadFile =
        (cbFileSizeHigh != 0
         ||  cbFileSizeLow > cbBuffer
         ||  !ReadFile(hFile, pbBuffer, cbFileSizeLow, &cbRead, NULL));

    CloseHandle(hFile);

    return fBadFile  ?  0  :  cbRead;
#else
    return 0;
#endif // !LKRHASH_KERNEL_MODE
}


// Do a case-insensitive match of first `cchStr' chars of ptszBuffer
// against ptszStr. Strings assumed to be alphanumeric
bool
StrMatch(
    LPCSTR   pszBuffer,
    LPCSTR   pszStr,
    unsigned cchStr)
{
    LPCSTR   psz1 = pszBuffer;
    LPCSTR   psz2 = pszStr;
    unsigned i, j;
    bool     fMatch = true;

    for (i = 0;  i < cchStr;  ++i)
    {
        IRTLASSERT(isalnum(*psz1) && isalnum(*psz2));

        if (((*psz1++ ^ *psz2++) & 0xDF) != 0)
        {
            fMatch = false;
            break;
        }
    }

    IRTLTRACE0("\tStrMatch: \"");
    for (j = 0;  j < i + !fMatch;  ++j)
        IRTLTRACE1("%hc", pszBuffer[j]);
    IRTLTRACE0(fMatch ? "\" == \"" : "\" != \"");
    for (j = 0;  j < i + !fMatch;  ++j)
        IRTLTRACE1("%hc", pszStr[j]);
    IRTLTRACE0("\"\n");


    return fMatch;
}


bool
GetNum(
    char*& rpch,
    int&   rn)
{
    int fNegative = ('-' == *rpch);
    rn = 0;

    if (fNegative)
        ++rpch;
    else if (!('0' <= *rpch  &&  *rpch <= '9'))
        return false;

    while ('0' <= *rpch  &&  *rpch <= '9')
        rn = rn * 10 + (*rpch++ - '0');

    if (fNegative)
        rn = -rn;

    return true;
}



bool
GetDouble(
    char*&  rpch,
    double& rdbl)
{
    rdbl = 0;

    int n;
    bool fValidInt = GetNum(rpch, n);

    if (fValidInt)
    {
        rdbl = n;

        // BUGBUG: ignore fractional part, if any
        if ('.' == *rpch)
        {
            ++rpch;
            GetNum(rpch, n);
        }
    }

    return fValidInt;
}



bool
GetString(
    char*&   rpch,
    char*    pszOutput,
    unsigned cchOutput)
{
    // TODO: handle quoted strings and trailing blanks

    bool fGotChars = false;
    
    while ('\0' != *rpch  &&  '\r' != *rpch  &&  '\n' != *rpch )
    {
        fGotChars = true;

        if (cchOutput-- > 0)
            *pszOutput++ = *rpch++;
        else
            ++rpch;
    }

    if (cchOutput > 0)
        *pszOutput = '\0';

    return fGotChars;
}
    


// TODO: break the dependency upon g_po.

int
CIniFileSettings::ParseIniFile(
	LPCSTR             pszIniFile)
{
    strncpy(m_tszIniFile, pszIniFile, _MAX_PATH);

    int i, iMaxIndex = -1;
    int cMembers = 0;

    for (i = 0;  ; ++i)
    {
        if (g_po[i].m_nFieldOffset < 0)
        {
            iMaxIndex = i;
            break;
        }

        PBYTE pbMember = g_po[i].m_nFieldOffset + ((BYTE*) this);

        // Initialize the members of `this' with their default values
        switch (g_po[i].m_type)
        {
        case INI_WORD:
            * (WORD*) pbMember = (WORD) g_po[i].m_default;
            break;
        case INI_DWORD:
            * (DWORD*) pbMember = (DWORD) g_po[i].m_default;
            break;
        case INI_DOUBLE:
            * (double*) pbMember = (float) g_po[i].m_default;
            break;
        case INI_STRING:
            strcpy((char*) pbMember, (const char*) g_po[i].m_default);
            break;
        case INI_BOOL:
            * (bool*) pbMember = (bool) (g_po[i].m_default != 0);
            break;
        default:
            IRTLASSERT(! "invalid INI_TYPE");
        }
    }

    DUMP_INIFILE(this, "Before", "");

    BYTE  abBuffer[2049];
    DWORD cbRead = ReadFileIntoBuffer(m_tszIniFile, abBuffer,
                                      sizeof(abBuffer)-1);

    if (cbRead == 0)
    {
        _tprintf(_TEXT("Can't open IniFile `%s'.\n"), m_tszIniFile) ;
        return 0;
    }

    abBuffer[cbRead] = '\0';

    bool  fInSection = false, fSkipLine = false;
    bool  fDone = false;
	const char szSectionName[] = "HashTest";
	unsigned   cchSectionName = strlen(szSectionName);
    char* pch = (char*) abBuffer;
    char* pszEOB = (char*) (abBuffer + cbRead);

    // parse the in-memory buffer
    while ('\0' != *pch)
    {
        while (' ' == *pch  ||  '\r' == *pch
               ||  '\n' == *pch  ||  '\t' == *pch)
            ++pch;

        if ('\0' == *pch)
            break;

        IRTLTRACE(_TEXT("Line starts with '%hc%hc%hc%hc'\n"),
                  pch[0], pch[1], pch[2], pch[3]);

        // Is this a section name?
        if ('[' == *pch)
        {
            fInSection = false;
            ++pch;
            while (' ' == *pch  ||  '\t' == *pch)
                ++pch;
            if (pch + cchSectionName < pszEOB
                &&  StrMatch(pch, szSectionName, cchSectionName))
            {
                pch += cchSectionName;
                while (' ' == *pch  ||  '\t' == *pch)
                    ++pch;
                if (']' == *pch)
                {
                    ++pch;
                    fInSection = true;
                }
            }
            else
                fSkipLine = true;

            continue;
        }

        // skip comments and entire lines if we're not in the right section
        if (fSkipLine  ||  ';' == *pch  ||  !fInSection)
        {
            // skip to end of line
            while ('\0' != *pch  &&  '\r' != *pch  &&  '\n' != *pch)
            {
                IRTLTRACE1("%hc", *pch);
                ++pch;
            }

            IRTLTRACE0("\n");
            fSkipLine = false;
            continue;
        }

        fSkipLine = true;

        // try to match name=value
        for (i = 0;  i < iMaxIndex;  ++i)
        {
            IRTLASSERT(isalnum(*pch));
            if (pch + g_po[i].m_cchName >= pszEOB
                || !StrMatch(pch, g_po[i].m_pszName, g_po[i].m_cchName))
                continue;

            pch += g_po[i].m_cchName;
            while (' ' == *pch  ||  '\t' == *pch)
                ++pch;
            if ('=' != *pch)
            {
                IRTLTRACE1("'=' not seen after <%hs>\n", g_po[i].m_pszName);
                break;
            }
            ++pch;
            while (' ' == *pch  ||  '\t' == *pch)
                ++pch;

            PBYTE  pbMember = g_po[i].m_nFieldOffset + ((BYTE*) this);
            int    n;
            char   sz[_MAX_PATH];
            double dbl;

            IRTLTRACE1("<%hs>=", g_po[i].m_pszName);

            switch (g_po[i].m_type)
            {
            case INI_WORD:
                if (GetNum(pch, n))
                {
                    IRTLTRACE1("%hu\n", (WORD) n);
                    * (WORD*) pbMember = (WORD) n;
                }
                else
                    IRTLTRACE("bad word\n");
                break;
            case INI_DWORD:
                if (GetNum(pch, n))
                {
                    IRTLTRACE1("%u\n", (DWORD) n);
                    * (DWORD*) pbMember = (DWORD) n;
                }
                else
                    IRTLTRACE("bad dword\n");
                break;
            case INI_DOUBLE:
                if (GetDouble(pch, dbl))
                {
                    IRTLTRACE1("%.1f\n", dbl);
                    * (double*) pbMember = dbl;
                }
                else
                    IRTLTRACE("bad double\n");
                break;
            case INI_STRING:
                if (GetString(pch, sz, sizeof(sz)/sizeof(sz[0])))
                {
                    IRTLTRACE1("%hs\n", sz);
                    strcpy((char*) pbMember, sz);
                }
                else
                    IRTLTRACE("bad string\n");
                break;
            case INI_BOOL:
                if (GetNum(pch, n))
                {
                    IRTLTRACE1("%d\n", n);
                    * (bool*) pbMember = (n != 0);
                }
                else
                    IRTLTRACE("bad bool\n");
                break;
            default:
                IRTLASSERT(! "invalid INI_TYPE");
            }

            ++cMembers;
            fSkipLine = false;
            break;
        }
    }

    DUMP_INIFILE(this, "Parsed", "---");

    return cMembers;
}


void
CIniFileSettings::ReadIniFile(
	LPCTSTR ptszIniFile)
{
    ParseIniFile(ptszIniFile);

    m_nMaxKeys = min(max(1, m_nMaxKeys),  MAXKEYS);
    m_dblHighLoad = max(1, m_dblHighLoad);
    m_nMinThreads = max(1, m_nMinThreads);
    m_nMaxThreads = min(MAX_THREADS,  max(1, m_nMaxThreads));

    // If we're not using a real lock, then we're not threadsafe
    if (CWordHash::TableLock::LockType() == LOCK_FAKELOCK
        ||  CWordHash::BucketLock::LockType() == LOCK_FAKELOCK)
        m_nMinThreads = m_nMaxThreads = 1;

    m_nRounds = max(1, m_nRounds);
    CWordHash::sm_fCaseInsensitive = m_fCaseInsensitive;
    CWordHash::sm_fMemCmp = m_fMemCmp;
    CWordHash::sm_nLastChars = m_nLastChars;
    CWordHash::sm_fNonPagedAllocs = m_fNonPagedAllocs;
    CWordHash::sm_fRefTrace = m_fRefTrace;

#ifdef LOCK_DEFAULT_SPIN_IMPLEMENTATION
# ifdef LKRHASH_GLOBAL_LOCK
    CWordHash::GlobalLock::SetDefaultSpinAdjustmentFactor(m_dblSpinAdjFctr);
# endif
    CWordHash::TableLock::SetDefaultSpinAdjustmentFactor(m_dblSpinAdjFctr);
    CWordHash::BucketLock::SetDefaultSpinAdjustmentFactor(m_dblSpinAdjFctr);
#endif // LOCK_DEFAULT_SPIN_IMPLEMENTATION

    if (CWordHash::TableLock::Recursion() != LOCK_RECURSIVE
        ||  CWordHash::BucketLock::LockType() != LOCK_RECURSIVE)
        m_nInsertIfNotFound = 0;
}