#include "windows.h"
#include "windowsx.h"
#include "shlwapi.h"
#include "commctrl.h"
#include "comctrlp.h"
#include <stdlib.h>
#include <stdio.h>

#define VERSION TEXT("0.00")
#define SIZEOF(x) sizeof(x)
#define ARRAYSIZE(x) (sizeof(x)/sizeof(x[0]))

typedef struct
{
    WORD wOrdinal;
    LPTSTR pFunction;
} EXPORTENTRY, * LPEXPORTENTRY;


//
// read a line, skipping leading and trailing white space and placing the output
// into the specified buffer.
//

LPTSTR _ReadLine(LPTSTR pSource, LPTSTR pBuffer, INT cchBuffer)
{
    //
    // skip leading white space
    //
    
    *pBuffer = TEXT('\0');

    while ( (*pSource == TEXT(' ')) ||
               (*pSource == TEXT('\t')) )
    {
        pSource++;
    }

    if ( !*pSource )
        return NULL;

    while ( (*pSource != TEXT('\r')) && 
              (*pSource != TEXT('\n')) && 
                (*pSource != TEXT('\0')) && 
                  (cchBuffer >= 1) )
    {   
        *pBuffer++ = *pSource++;
        cchBuffer--;
    }

    *pBuffer++ = TEXT('\0');

    while ( (*pSource == TEXT('\r')) ||
              (*pSource == TEXT('\n')) )
    {
        pSource++;
    }

    return pSource;
}


//
// Get string element, given an index into the buffer copy out the element
// that we want.
//

BOOL _GetStringElement(LPTSTR pString, INT index, BOOL fEntireLine, LPTSTR pBuffer, INT cchBuffer)
{
    for ( ; *pString && (index > 0) ; index-- )
    {
        while ( *pString != TEXT(',') && *pString != TEXT('\0') )
            pString++;

        if ( *pString == TEXT(',') )
            pString++;
    }

    if ( index )
        return FALSE;        

    while ( *pString == TEXT(' ') )
        pString++;

    while ( *pString && (cchBuffer > 1) )
    {
        if ( !fEntireLine && (*pString == TEXT(',')) )
            break;
            
        *pBuffer++ = *pString++;
        cchBuffer--;
    }

    *pBuffer = TEXT('\0');

    return TRUE;
}


//
// Get a stub function name given its module and function name.
//

static TCHAR szStubFunction[MAX_PATH];

LPTSTR _GetStubFunction(LPTSTR pModule, LPTSTR pFunction)
{
    wnsprintf(szStubFunction, ARRAYSIZE(szStubFunction), TEXT("_%s_%s"), pModule, pFunction);
    return szStubFunction;
}


//
// Generate stub
//
// This takes a line from the file and get the information we need from it.
//

BOOL _GenerateStub(LPTSTR pModule, LPTSTR pBuffer, HDPA hdpaFunctions, HDPA hdpaOrdinals)
{
    TCHAR szResultType[MAX_PATH];
    TCHAR szResult[MAX_PATH];
    TCHAR szFunction[MAX_PATH];
    TCHAR szArguments[MAX_PATH*2];
    LPTSTR pFunction;
    LPTSTR pOrdinal;
    INT iByName, iByOrdinal;
    LPEXPORTENTRY pExport;

    // get the fields, all are required

    if ( !_GetStringElement(pBuffer, 0, FALSE, szResultType, ARRAYSIZE(szResultType)) )
        return FALSE;

    if ( !_GetStringElement(pBuffer, 1, FALSE, szResult, ARRAYSIZE(szResult)) )
        return FALSE;

    if ( !_GetStringElement(pBuffer, 2, FALSE, szFunction, ARRAYSIZE(szFunction)) )
        return FALSE;

    if ( !_GetStringElement(pBuffer, 3, TRUE, szArguments, ARRAYSIZE(szArguments)) )
        return FALSE;

    // if the function name is bla@4 then it has an ordinal therefore we must attempt
    // to get the ordinal number.

    pOrdinal = StrChr(szFunction, TEXT('@'));
    if ( pOrdinal )
        *pOrdinal++ = TEXT('\0');

    // allocate an export, adding both the ordinals and the functions as required.
    // if pOrdinal != NULL then we assume that we should parse the int.

    pExport = LocalAlloc(LPTR, SIZEOF(EXPORTENTRY));
    if ( !pExport )
        return FALSE;    

    Str_SetPtr(&pFunction, szFunction);
    if ( !pFunction )
    {
        LocalFree(pExport);
        return FALSE;
    }

    pExport->wOrdinal = (WORD) StrToInt(pOrdinal ? pOrdinal:TEXT(""));
    pExport->pFunction = pFunction;

    iByOrdinal = iByName = DPA_AppendPtr(hdpaFunctions, pExport);
    
    if ( pOrdinal )
        iByOrdinal = DPA_AppendPtr(hdpaOrdinals, pExport);

    if ( (iByName == -1) || (iByOrdinal == -1) )
    {
        if ( iByName != -1 )
            DPA_DeletePtr(hdpaFunctions, iByName);

        LocalFree(pExport);
        Str_SetPtr(&pFunction, NULL);
        return FALSE;
    }

    // spew out the function name

    printf(TEXT("\n"));
    printf(TEXT("%s %s%s\n"), szResultType, _GetStubFunction(pModule, pFunction), szArguments);
    printf(TEXT("{\n"));

    if ( szResult[0] )
        printf(TEXT("   return %s;\n"), szResult);

    printf(TEXT("}\n"));
    
    return TRUE;
}


//
// "stubgen <stub list> <module>"
//
// The stub list is a text file that lists all the exports you want to generate
// stubs for, each stub is a simple function which returns a specified result.
//
// The format of the file is:
//
//    <result type>,<result>,<function>,<arguments>
//
// eg:
//
//    BOOL, FALSE, SHBrowseForContainer, (bla, bla, bla)
//
// Which generates a stub:
//
//    BOOL SHBrowseForContainer(bla, bla, bla)
//    {
//      return FALSE;
//    }
//

INT _SortNameCB(LPVOID p1, LPVOID p2, LPARAM lParam)
{
    LPEXPORTENTRY pExport1 = (LPEXPORTENTRY)p1;
    LPEXPORTENTRY pExport2 = (LPEXPORTENTRY)p2;
    return StrCmpI(pExport1->pFunction, pExport2->pFunction);
}

INT _SortOrdinalCB(LPVOID p1, LPVOID p2, LPARAM lParam)
{
    LPEXPORTENTRY pExport1 = (LPEXPORTENTRY)p1;
    LPEXPORTENTRY pExport2 = (LPEXPORTENTRY)p2;
    return pExport1->wOrdinal - pExport2->wOrdinal;
}

INT __cdecl main(INT cArgs, LPTSTR pArgs[])
{
    TCHAR szSource[MAX_PATH];
    TCHAR szModule[MAX_PATH];
    HANDLE hFile;
    LPTSTR pStubFile;
    DWORD dwSize, dwRead;
    HDPA hdpaFunctions;
    HDPA hdpaOrdinals;
    INT i;

    if ( cArgs < 2 )
    {
        printf(TEXT("stubgen: <src> <module>\n"));
        return -1;
    }

    StrCpy(szSource, pArgs[1]);
    StrCpy(szModule, pArgs[2]);


    //
    // load the source file into memory and then lets generate the stub table,
    // add a TCHAR to the file size to get it null terminated
    //

    hFile = CreateFile(szSource,
                       GENERIC_READ,
                       FILE_SHARE_READ,
                       NULL, 
                       OPEN_EXISTING,
                       FILE_ATTRIBUTE_NORMAL,
                       NULL);

    if ( hFile == INVALID_HANDLE_VALUE )
        return -1;

    dwSize = GetFileSize(hFile, NULL);
    pStubFile = LocalAlloc(LPTR, dwSize+SIZEOF(TCHAR));

    if ( !pStubFile ||
           !ReadFile(hFile, pStubFile, dwSize, &dwRead, NULL) ||
             dwRead != dwSize )
    {
        CloseHandle(hFile);
        return -1;
    }

    CloseHandle(hFile);    


    //
    // Create the DPA we will use for storing the function names
    //

    hdpaFunctions = DPA_Create(16);
    hdpaOrdinals = DPA_Create(16);

    if ( !hdpaFunctions || ! hdpaOrdinals )
        return -1;


    //
    // output header information
    // 

    for ( i = 3 ; i < cArgs ; i++ )
        printf(TEXT("#include \"%s\"\n"), pArgs[i]);

    printf(TEXT("#pragma hdrstop\n"));

    printf(TEXT("\n"));
    printf(TEXT("// Generate from %s by stubgen.exe\n"), szSource);
    printf(TEXT("// *** DO NOT EDIT THIS FILE ***\n\n"));


    //
    // now lets parse the file, trying to the function prototypes from it,
    // we skip all lines that start with a ';', '#' or '/' (as in //)
    //      

    while ( pStubFile )
    {
        TCHAR szBuffer[1024];

        pStubFile = _ReadLine(pStubFile, szBuffer, ARRAYSIZE(szBuffer));
        
        if ( pStubFile )
        {
            switch ( szBuffer[0] )
            {
                case TEXT('#'):
                case TEXT(';'):
                case TEXT('/'):
                    // comments are stripped
                    break;

                default:
                    _GenerateStub(szModule, szBuffer, hdpaFunctions, hdpaOrdinals);
                    break;
            }
        }
    }   

    
    //
    // if hdpaFunctions contains anything then we have generated a set of
    // stubs, so lets sort it and output that.
    //

    if ( DPA_GetPtrCount(hdpaFunctions) )
    {
        DPA_Sort(hdpaFunctions, _SortNameCB, 0);

        printf(TEXT("\n"));
        printf(TEXT("const INT g_c%sExportTable = %d;\n"), szModule, DPA_GetPtrCount(hdpaFunctions));
        printf(TEXT("const EXPORTTABLE g_%sExportTable[] =\n"), szModule);
        printf(TEXT("{\n"));

        for ( i = 0 ; i < DPA_GetPtrCount(hdpaFunctions); i++ )
        {
            LPEXPORTENTRY pExport = (LPEXPORTENTRY)DPA_GetPtr(hdpaFunctions, i);
            TCHAR szBuffer[MAX_PATH];

            StrCpy(szBuffer, pExport->pFunction);
#if UNICODE
            _wcslwr(szBuffer);
#else
            _strlwr(szBuffer);
#endif

            printf(TEXT("    \"%s\", (FARPROC)%s,\n"), szBuffer, _GetStubFunction(szModule, pExport->pFunction));
        }

        printf(TEXT("};\n"));
    }

    if ( DPA_GetPtrCount(hdpaOrdinals) )
    {
        DPA_Sort(hdpaFunctions, _SortOrdinalCB, 0);

        printf(TEXT("\n"));
        printf(TEXT("const INT g_c%sOrdinalTable = %d;\n"), szModule, DPA_GetPtrCount(hdpaOrdinals));
        printf(TEXT("const ORDINALTABLE g_%sOrdinalTable[] =\n"), szModule);
        printf(TEXT("{\n"));

        for ( i = 0 ; i < DPA_GetPtrCount(hdpaOrdinals); i++ )
        {
            LPEXPORTENTRY pExport = (LPEXPORTENTRY)DPA_GetPtr(hdpaOrdinals, i);
            printf(TEXT("    %d, (FARPROC)%s,\n"), pExport->wOrdinal, _GetStubFunction(szModule, pExport->pFunction));
        }

        printf(TEXT("};\n"));

    }

    return 0;
}
