/****************************** Module Header ******************************\
* Module Name: hsplit.c
*
* Structure parser - struct field name-offset tabel generator.
*
* Copyright (c) 1985-96, Microsoft Corporation
*
* 09/05/96 GerardoB Created
\***************************************************************************/
#include "hsplit.h"

/*
 * Maximum size of tags (gphst) table including possible user defined tags
 */
#define HSTAGTABLESIZE (sizeof(ghstPredefined) + ((32 - HST_MASKBITCOUNT) * sizeof(HSTAG)))
/***************************************************************************\
* hsAddTag
*
\***************************************************************************/
PHSTAG hsAddTag (char * pszTag, DWORD dwMask)
{
    PHSTAG phst;
    DWORD dwTagSize;

    /*
     * Make sure we still have mask bits to uniquely identified this tag
     */
    if (((dwMask | HST_EXTRACT) == HST_EXTRACT) && (gdwLastTagMask == HST_MAXMASK)) {
        hsLogMsg(HSLM_ERROR, "Too many user defined tags. Max allowed: %d", 32 - HST_MASKBITCOUNT);
        return NULL;
    }

    /*
     * Create the table the first time around.
     */
    if (gphst == ghstPredefined) {
        gphst = (PHSTAG) LocalAlloc(LPTR, HSTAGTABLESIZE);
        if (gphst == NULL) {
            hsLogMsg(HSLM_APIERROR, "LocalAlloc");
            hsLogMsg(HSLM_ERROR, "hsAddTag Allocation failed. Size:%#lx", HSTAGTABLESIZE);
            return NULL;
        }

        CopyMemory(gphst, &ghstPredefined, sizeof(ghstPredefined));
    }

    /*
     * If the string is in the table, we update the mask.
     */
    dwTagSize = strlen(pszTag);
    phst = hsFindTagInList(gphst, pszTag, dwTagSize);
    if (phst == NULL) {
        /*
         * New string. Find next available entry in the table
         */
        phst = gphst;
        while (phst->dwLabelSize != 0) {
            phst++;
        }
    }

    /*
     * Initialize it
     */
    phst->dwLabelSize = dwTagSize;
    phst->pszLabel = pszTag;

    /*
     * If generating a mask, use the next available bit in the tag mask
     *  else use the one supplied by the caller
     */
    if ((dwMask | HST_EXTRACT) == HST_EXTRACT) {
        gdwLastTagMask *= 2;
        phst->dwMask = (gdwLastTagMask | dwMask);
    } else {
        phst->dwMask = dwMask;
    }

    /*
     * Add this tag's mask to the filter mask so lines mark with this tag
     *  will be included
     */
    gdwFilterMask |= (phst->dwMask & HST_USERTAGSMASK);

    return phst;
}
/***************************************************************************\
* hsIsSwitch
*
\***************************************************************************/
__inline BOOL hsIsSwitch(char c)
{
    return (c == '/') || (c == '-');
}

/***************************************************************************\
* hsAddUserDefinedTag
*
\***************************************************************************/

BOOL hsAddUserDefinedTag(DWORD* pdwMask, int* pargc, char*** pargv)
{
    DWORD  dwRetMask = *pdwMask;
    PHSTAG phst;

    if (*pargc < 2) {
        return FALSE;  // invalid switch
    }
    
    /*
     * Allow multiple tags to be specified for one switch
     *  i.e., -t tag1 <tag2 tag2....>
     */
    do {
        (*pargc)--, (*pargv)++;

        /*
         * Add tag to table
         */
        phst = hsAddTag(**pargv, *pdwMask);
        if (phst == NULL) {
            return 0;
        }
        
        dwRetMask |= phst->dwMask;

    } while ((*pargc >= 2) && !hsIsSwitch(**(*pargv + 1)));

    /*
     * save the new mask
     */
    *pdwMask = dwRetMask;

    return TRUE;
}

/***************************************************************************\
* hsAddExtractFile
*
\***************************************************************************/
BOOL hsAddExtractFile(char* pszExtractFile, DWORD dwMask, BOOL bAppend)
{
    PHSEXTRACT pe;

    pe = LocalAlloc(LPTR, sizeof(HSEXTRACT));
    
    if (pe == NULL) {
        return FALSE;
    }
    pe->pszFile = pszExtractFile;
    pe->dwMask  = dwMask;

    pe->hfile = CreateFile(pszExtractFile, GENERIC_WRITE, 0, NULL,
                            (bAppend ? OPEN_EXISTING : CREATE_ALWAYS),
                            FILE_ATTRIBUTE_NORMAL,  NULL);

    if (pe->hfile == INVALID_HANDLE_VALUE) {
        hsLogMsg(HSLM_APIERROR | HSLM_NOLINE, "CreateFile failed for file %s",
                 pszExtractFile);
        LocalFree(pe);
        return FALSE;
    }
    if (bAppend) {
        if (0xFFFFFFFF == SetFilePointer (pe->hfile, 0, 0, FILE_END)) {
            hsLogMsg(HSLM_APIERROR | HSLM_NOLINE, "SetFilePointer failed for file %s",
                     pszExtractFile);
            CloseHandle(pe->hfile);
            LocalFree(pe);
            return FALSE;
        }
    }
    
    /*
     * link it in the list of extract files
     */
    pe->pNext = gpExtractFile;
    gpExtractFile = pe;
    
    return TRUE;
}

/***************************************************************************\
* hsProcessParameters
*
\***************************************************************************/
int hsProcessParameters(int argc, LPSTR argv[])
{
    char c, *p;
    DWORD dwMask;
    int argcParm = argc;
    PHSTAG phst;

    /*
     * Compatibility. Assume default project the first time this
     *  function is called
     */
    if (!(gdwOptions & HSO_APPENDOUTPUT)) {
        gdwOptions |= HSO_OLDPROJSW_N;
    }

    /*
     * Loop through parameters.
     */
    while (--argc) {
        p = *++argv;
        if (hsIsSwitch(*p)) {
            while (c = *++p) {
                switch (toupper(c)) {
                /*
                 * Compatibility.
                 * Chicago/Nashvilled header.
                 */
               case '4':
                   gdwOptions &= ~HSO_OLDPROJSW;
                   gdwOptions |= HSO_OLDPROJSW_4;
                   break;

                /*
                 * Old bt2 and btb switches to replace internal and
                 *  both block tags.
                 */
                case 'B':
                   p++;
                   if (toupper(*p++) != 'T') {
                       goto InvalidSwitch;
                   }
                   if (toupper(*p) == 'B') {
                       dwMask = HST_BOTH | HST_USERBOTHBLOCK;
                       gdwOptions |= HSO_USERBOTHBLOCK;
                   } else if (*p == '2') {
                       dwMask = HST_INTERNAL | HST_USERINTERNALBLOCK;
                       gdwOptions |= HSO_USERINTERNALBLOCK;
                   } else {
                       goto InvalidSwitch;
                   }

                   if (argc < 3) {
                       goto InvalidSwitch;
                   }


                   /*
                    * Add these strings as tags and mark them as blocks
                    */
                   argc--, argv++;
                   phst = hsAddTag(*argv, HST_BEGIN | dwMask);
                   if (phst == NULL) {
                       return 0;
                   }

                   argc--, argv++;
                   phst = hsAddTag(*argv, HST_END | dwMask);
                   if (phst == NULL) {
                       return 0;
                   }
                   break;

                /*
                 * Tag marker
                 */
               case 'C':
                   if (argc < 2) {
                       goto InvalidSwitch;
                   }

                   argc--, argv++;
                   gpszTagMarker = *argv;
                   *gszMarkerCharAndEOL = *gpszTagMarker;
                   gdwTagMarkerSize = strlen(gpszTagMarker);
                   if (gdwTagMarkerSize == 0) {
                       goto InvalidSwitch;
                   }
                   break;

                /*
                 * Compatibility.
                 * NT SUR header
                 */
               case 'E':
                   gdwOptions &= ~HSO_OLDPROJSW;
                   gdwOptions |= HSO_OLDPROJSW_E;
                   break;

                /*
                 * Input file
                 */
                case 'I':
                    if (argc < 2) {
                        goto InvalidSwitch;
                    }

                    argc--, argv++;
                    gpszInputFile = *argv;
                    goto ProcessInputFile;
                    break;

                /*
                 * Extract file
                 */
                case 'X':
                    {
                        char* pszExtractFile;
                        BOOL  bAppend = FALSE;
                        
                        if (toupper(*(p+1)) == 'A') {
                            p++;
                            bAppend = TRUE;
                        }
                        
                        if (argc < 3) {
                            goto InvalidSwitch;
                        }
    
                        argc--, argv++;
                        pszExtractFile = *argv;
    
                        dwMask = HST_EXTRACT;
                        if (!hsAddUserDefinedTag(&dwMask, &argc, &argv))
                            goto InvalidSwitch;
                        
                        hsAddExtractFile(pszExtractFile, dwMask, bAppend);
                        
                        break;
                    }

                /*
                 * Old lt2 and ltb switches to replace internal and
                 *  both tags.
                 */
               case 'L':
                   p++;
                   if (toupper(*p++) != 'T') {
                       goto InvalidSwitch;
                   }
                   if (toupper(*p) == 'B') {
                       dwMask = HST_BOTH | HST_USERBOTHTAG;
                       gdwOptions |= HSO_USERBOTHTAG;
                   } else if (*p == '2') {
                       dwMask = HST_INTERNAL | HST_USERINTERNALTAG;
                       gdwOptions |= HSO_USERINTERNALTAG;
                   } else {
                       goto InvalidSwitch;
                   }

                   if (!hsAddUserDefinedTag(&dwMask, &argc, &argv))
                       goto InvalidSwitch;
                   
                   break;

                /*
                 * Compatibility.
                 * NT header
                 */
               case 'N':
                    gdwOptions &= ~HSO_OLDPROJSW;
                    gdwOptions |= HSO_OLDPROJSW_N;
                   break;

                /*
                 * Ouput files
                 */
                case 'O':
                    if (argc < 3) {
                        goto InvalidSwitch;
                    }

                    argc--, argv++;
                    gpszPublicFile = *argv;

                    argc--, argv++;
                    gpszInternalFile = *argv;
                    break;

                /*
                 * Compatibility.
                 * NT SURPlus header
                 */
               case 'P':
                   gdwOptions &= ~HSO_OLDPROJSW;
                   gdwOptions |= HSO_OLDPROJSW_P;
                   break;

                /*
                 * Split only. Process internal/both tags only. Tags
                 *  including other tags as well (i.e., internal_NT)
                 *  are treated as untagged.
                 */
               case 'S':
                   gdwOptions |= HSO_SPLITONLY;
                   break;

                /*
                 * User defined tags.
                 */
                case 'T':
                    switch (toupper(*++p)) {
                        /*
                         * Include lines mark with this tag
                         */
                        case 'A':
                            dwMask = 0;
                            break;
                        /*
                         * Ignore lines marked with this tag (i.e, treated
                         *  as untagged)
                         */
                        case 'I':
                            dwMask = HST_IGNORE;
                            break;
                        /*
                         * Skip lines marked with this tag (i.e., not
                         *  included in header files)
                         */
                        case 'S':
                            dwMask = HST_SKIP;
                            break;

                        default:
                            goto InvalidSwitch;
                    }

                    if (!hsAddUserDefinedTag(&dwMask, &argc, &argv))
                        goto InvalidSwitch;

                    break;

                /*
                 * Version
                 */
                case 'V':
                    if (argc < 2) {
                        goto InvalidSwitch;
                    }

                    argc--, argv++;
                    if (!hsVersionFromString (*argv, strlen(*argv), &gdwVersion)) {
                        goto InvalidSwitch;
                    }
                    break;

                /*
                 * Unknown tags are to be skipped, as opposed to ignored.
                 */
               case 'U':
                   gdwOptions |= HSO_SKIPUNKNOWN;
                   break;

                /*
                 * Invalid switch
                 */
                default:
InvalidSwitch:
                    hsLogMsg(HSLM_ERROR | HSLM_NOLINE, "Invalid switch or parameter: %c", c);
                    // Fall through

                /*
                 * Help
                 */
                case '?':
                   goto PrintHelp;

                } /* switch (toupper(c)) */
            } /* while (c = *++p) */
        } else { /* hsIsSwitch(*p) { */
            /*
             * No switch specified. Process this input file
             */
            gpszInputFile = *argv;
            break;
        }
    } /* while (--argc) */

ProcessInputFile:
    /*
     * Make sure we got input and ouput files.
     */
    if ((gpszInputFile == NULL)
            || (gpszPublicFile == NULL)
            || (gpszInternalFile == NULL)) {

        hsLogMsg(HSLM_ERROR | HSLM_NOLINE, "Missing input or ouput file");
        goto PrintHelp;
    }

    /*
     * Add compatibility tags for default projects (first call only)
     */
    if ((gdwOptions & HSO_OLDPROJSW) && !(gdwOptions & HSO_APPENDOUTPUT)) {
        if (!(gdwOptions & HSO_OLDPROJSW_4)) {
            phst = hsAddTag(gszNT, 0);
            if (phst == NULL) {
                return 0;
            }
            phst->dwMask |= HST_MAPOLD;
            dwMask = phst->dwMask;
        }

        if (gdwOptions & HSO_OLDPROJSW_E) {
            hsAddTag(gszCairo, dwMask);
            hsAddTag(gszSur, dwMask);
            hsAddTag(gszWin40, dwMask);
            hsAddTag(gsz35, dwMask);

        } else if (gdwOptions & HSO_OLDPROJSW_P) {
            hsAddTag(gszWin40, dwMask);
            hsAddTag(gszWin40a, dwMask);
            hsAddTag(gszCairo, dwMask);
            hsAddTag(gszSur, dwMask);
            hsAddTag(gszSurplus, dwMask);
            hsAddTag(gsz35, dwMask);

        } else if (gdwOptions & HSO_OLDPROJSW_4) {
            gdwOptions |= HSO_INCINTERNAL;
            phst = hsAddTag(gszChicago, 0);
            if (phst == NULL) {
                return 0;
            }
            phst->dwMask |= HST_MAPOLD;
            dwMask = phst->dwMask;
            hsAddTag(gszNashville, dwMask);
            hsAddTag(gszWin40, dwMask);
            hsAddTag(gszWin40a, dwMask);

        } else if (!(gdwOptions & HSO_APPENDOUTPUT)) {
            gdwOptions |= HSO_OLDPROJSW_N;
        }

    } /* (gdOptions & HSO_OLDPROJW) */


    /*
     * Compatibility. If doing split only, don't include internal tags
     *  in public file
     */
    if (gdwOptions & HSO_SPLITONLY) {
        gdwOptions &= ~HSO_INCINTERNAL;
    }

    return argcParm - argc;

PrintHelp:
    hsLogMsg(HSLM_DEFAULT, "Header Split Utility. Version 2.1");
    hsLogMsg(HSLM_NOLABEL, "Usage: hsplit [options] <-o PublicFile InternalFile> [-i] File1 [-i] File2...");
    hsLogMsg(HSLM_NOLABEL, "\t[-4] Generate chicago/nashville headers");
    hsLogMsg(HSLM_NOLABEL, "\t[-bt2 BeginStr EndStr] Replace begin_internal/end_internal - Obsolete");
    hsLogMsg(HSLM_NOLABEL, "\t[-btb BeginStr EndStr] Replace begin_both/end_both tags - Obsolete");
    hsLogMsg(HSLM_NOLABEL, "\t[-c TagMarker] Replace tag marker. default \";\"");
    hsLogMsg(HSLM_NOLABEL, "\t[-e] Generate NT sur headers");
    hsLogMsg(HSLM_NOLABEL, "\t[[-i] file1 file2 ..] Input files - Required");
    hsLogMsg(HSLM_NOLABEL, "\t[-lt2 str] Replace internal tag - Obsolete");
    hsLogMsg(HSLM_NOLABEL, "\t[-ltb str] Replace both tag - Obsolete");
    hsLogMsg(HSLM_NOLABEL, "\t[-n] Generate NT headers - default");
    hsLogMsg(HSLM_NOLABEL, "\t[-x[a] ExtractHeader ExtractTag] Extract files and tags files");
    hsLogMsg(HSLM_NOLABEL, "\t[-o PublicHeader InternalHeader] Output files - Required");
    hsLogMsg(HSLM_NOLABEL, "\t[-p] Generate NT surplus headers");
    hsLogMsg(HSLM_NOLABEL, "\t[-s] Process internal and both tags only");
    hsLogMsg(HSLM_NOLABEL, "\t[-ta tag1 tag2 ..] Include lines using these tags");
    hsLogMsg(HSLM_NOLABEL, "\t[-ti tag1 tag2 ..] Ignore these tags");
    hsLogMsg(HSLM_NOLABEL, "\t[-ts tag1 tag2 ..] Skip lines using these tags");
    hsLogMsg(HSLM_NOLABEL, "\t[-u] Skip unknown tags. Default: ignore");
    hsLogMsg(HSLM_NOLABEL, "\t[-v] Version number. Default: LATEST_WIN32_WINNT_VERSION");
    hsLogMsg(HSLM_NOLABEL, "\r\nTags Format:");
    hsLogMsg(HSLM_NOLABEL, "\t<TagMarker>[begin/end][_public/internal][[_tag1][_tag2]...][_if_(str)_version | _version]");
    return 0;
}
/***************************************************************************\
* main
*
\***************************************************************************/
int __cdecl main (int argc, char *argv[])
{
    int argcProcessed;

    /*
     * Each loop processes one input file
     */
    do {
        argcProcessed = hsProcessParameters(argc, argv);
        if (argcProcessed == 0) {
            break;
        }

        if (!hsOpenWorkingFiles()
                || !hsSplit()) {

            return TRUE;
        }

        hsCloseWorkingFiles();

        gdwOptions |= HSO_APPENDOUTPUT;

        argc -= argcProcessed;
        argv += argcProcessed;

    } while (argc > 1);

    return FALSE;
}