/***************************************************************************/
/* SORT.CPP                                                                 */
/* Copyright (C) 1995-97 SYWARE Inc., All rights reserved                  */
/***************************************************************************/

// Commenting #define out - causing compiler error - not sure if needed, compiles
// okay without it.
//#define WINVER 0x0400
#include "precomp.h"
#include "wbemidl.h"

#include <comdef.h>
//smart pointer
_COM_SMARTPTR_TYPEDEF(IWbemServices,                IID_IWbemServices);
_COM_SMARTPTR_TYPEDEF(IEnumWbemClassObject,         IID_IEnumWbemClassObject);
//_COM_SMARTPTR_TYPEDEF(IWbemContext,                 IID_IWbemContext );
_COM_SMARTPTR_TYPEDEF(IWbemLocator,                 IID_IWbemLocator);

#include "drdbdr.h"
/***************************************************************************/
#define SORT_MAX_FIELDS 20

typedef struct  tagSORT_SECTION {
    UWORD left;
    UWORD right;
} SORT_SECTION, FAR * SORT_SECTIONS;
        
typedef struct  tagSORT_KEY {
    UWORD offset;
    UWORD length;
    UCHAR type;
    BOOL  descending;
} SORT_KEY;

typedef struct  tagSORT_DATA {
    UWORD dupKeyLen;   /* Length of duplicate key (0 if no dup removal) */
    UWORD dupKeyStart; /* Zero-based offset of duplicate key */
    SORT_KEY key[SORT_MAX_FIELDS];
    UWORD keyCount;
    UWORD recordSize;
    UDWORD recordStart;
    UWORD recordCount;
    UWORD bufferRecordCount;
    LPSTR dataBuffer;
} SORT_DATA;

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

BOOL INTFUNC SortKeyCompare(SORT_DATA FAR *sortData, LPSTR leftRecordPtr,
                                     LPSTR rightRecordPtr)

/* Is the key of the left greater than or equal the key of the right? */

{
    SORT_KEY FAR *keyComponent;
    UWORD idx;
    LPSTR leftValuePtr;
    LPSTR rightValuePtr;
    int compare;
    UWORD leftDecimalPos;
    UWORD rightDecimalPos;

    /* Compare each key component */
    keyComponent = sortData->key;
    for (idx = 0; idx < sortData->keyCount; idx++) {

        /* Point at the values */
        leftValuePtr = leftRecordPtr + keyComponent->offset;
        rightValuePtr = rightRecordPtr + keyComponent->offset;

        /* Compare the values */
        switch (keyComponent->type) {
        case 'E':
            if ((*((double FAR *) leftValuePtr)) <
                                  (*((double FAR *) rightValuePtr)))
                compare = -1;
            else if ((*((double FAR *) leftValuePtr)) >
                                  (*((double FAR *) rightValuePtr)))
                compare = 1;   
            else
                compare = 0;
            break;
        case 'N':
            if (*rightValuePtr != '-') {
                if (*leftValuePtr != '-') {
                    for (leftDecimalPos = 0;
                         leftDecimalPos < keyComponent->length;
                         leftDecimalPos++) {
                       if ((*leftValuePtr == '.') || (*leftValuePtr == ' '))
                           break;
                       leftValuePtr++;
                    }
                    for (rightDecimalPos = 0;
                         rightDecimalPos < keyComponent->length;
                         rightDecimalPos++) {
                       if ((*rightValuePtr == '.') || (*rightValuePtr == ' '))
                           break;
                       rightValuePtr++;
                    }
                    if (leftDecimalPos > rightDecimalPos)
                        compare = 1;
                    else if (leftDecimalPos < rightDecimalPos)
                        compare = -1;
                    else {
                        leftValuePtr = leftRecordPtr + keyComponent->offset;
                        rightValuePtr = rightRecordPtr + keyComponent->offset;
                        compare = _fmemcmp(leftValuePtr, rightValuePtr,
                                        keyComponent->length);
                    }
                }
                else {
                    compare = -1;
                }
            }
            else {
                if (*leftValuePtr != '-') {
                    compare = 1;
                }
                else {
                    for (leftDecimalPos = 0;
                         leftDecimalPos < keyComponent->length;
                         leftDecimalPos++) {
                       if ((*leftValuePtr == '.') || (*leftValuePtr == ' '))
                           break;
                       leftValuePtr++;
                    }
                    for (rightDecimalPos = 0;
                         rightDecimalPos < keyComponent->length;
                         rightDecimalPos++) {
                       if ((*rightValuePtr == '.') || (*rightValuePtr == ' '))
                           break;
                       rightValuePtr++;
                    }
                    if (leftDecimalPos > rightDecimalPos)
                        compare = 1;
                    else if (leftDecimalPos < rightDecimalPos)
                        compare = -1;
                    else {
                        leftValuePtr = leftRecordPtr + keyComponent->offset;
                        rightValuePtr = rightRecordPtr + keyComponent->offset;
                        compare = _fmemcmp(leftValuePtr, rightValuePtr,
                                        keyComponent->length);
                    }
                    compare = -(compare);
                }
            }
            break;
        case 'C':
            compare = _fmemcmp(leftValuePtr, rightValuePtr,
                               keyComponent->length);
            break;
        }

        /* Adjust for ascending/descending */
        if (keyComponent->descending)
            compare = -compare;

        /* Return result */
        if (compare < 0)
            return FALSE;
        if (compare > 0)
            return TRUE;

        /* Look at next component */
        keyComponent++;
    }
    return TRUE;
}
/***************************************************************************/
RETCODE INTFUNC SortInstructions(LPSTR lpszInstructions,
                              SORT_DATA FAR *sortData)

/* Parse the instruction string.  The instruction string is in the form:    */
/*                                                                          */
/*   instuction_string ::= item | item instruction_string                   */
/*                                                                          */
/*   item ::= sort_item | fix_item | workdrive_item | dupout_item           */
/*                                                                          */
/*   sort_item ::= S(sort_list)                                             */
/*                                                                          */
/*   sort_list ::= sort_list_item | sort_list_item,sort_list                */
/*                                                                          */
/*   sort_list_item ::= offset,length,type,ascending_or_descending          */
/*                                                                          */
/*   offset ::= << 1-based record offset of sort key component >>           */
/*                                                                          */
/*   length ::= << length of sort key component >>                          */
/*                                                                          */
/*   type ::= type_double | type_number | type_character | type_binary      */
/*                                                                          */
/*   type_double ::= E                                                      */
/*                                                                          */
/*   type_ascii_numeric ::= N                                               */
/*                                                                          */
/*   type_character ::= C                                                   */
/*                                                                          */
/*   type_binary ::= W                                                      */
/*                                                                          */
/*   ascending_or_descending := ascending | descending                      */
/*                                                                          */
/*   ascending := A                                                         */
/*                                                                          */
/*   descending := D                                                        */
/*                                                                          */
/*   fix_item ::= F(FIX,recordsize)                                         */
/*                                                                          */
/*   recordsize ::= << size of the records in the sort file in bytes >>     */
/*                                                                          */
/*   workdrive_item ::= W(workdirive)                                       */
/*                                                                          */
/*   workdrive ::= << pathname of directory to hold temporary files >>      */
/*                                                                          */
/*   dupout_item ::= DUPO(Bdupout_buffer,dupout_offset,dupout_length)       */
/*                                                                          */
/*   dupout_buffer ::= << size of buffer to use during duplicate removal >> */
/*                                                                          */
/*   dupout_offset ::= << 1-based record offset of duplicate removal key >> */
/*                                                                          */
/*   dupout_length ::= << length duplicate removal key >>                   */
/*                                                                          */
/*      Notes: (1) If the sort key item is ascii_numeric, it is a number    */
/*                 represented as an ASCII string.  All the numbers in the  */
/*                 file must have the same number of digits to the right of */
/*                 the decimal point.  If there are no digits to the right  */
/*                 of the decimal point, the decimal point is not required, */
/*                 but if one record has a decimal point, they all have to  */
/*                 have decimal points.                                     */
/*                                                                          */
/*             (2) The workdrive is assumed to include a trailing backslash */
/*                                                                          */
/*             (3) The instruction string may not have embedded white space */
/*                                                                          */
/* This parser assumes that the instruction string given adheres to the     */
/* syntax above.  If the instruction string given is malformed, the results */
/* are undefined.                                                           */
{
    /* Get sorting instructions */
    sortData->dupKeyLen = 0;
    sortData->dupKeyStart = 0;
    sortData->recordSize = 0;
    sortData->keyCount = 0;
    while (*lpszInstructions) {

        /* Get the command */
        switch (*lpszInstructions) {
        case 'S':

            /* Parse: S(offset,length,type,asc,offset,length,type,asc,...) */
            lpszInstructions++;          /* 'S' */
            do {
                
                if (sortData->keyCount >= SORT_MAX_FIELDS) {
                    return ERR_SORT;
                }

                lpszInstructions++;      /* '(' or ',' */

                sortData->key[sortData->keyCount].offset = 0;
                do {
                    sortData->key[sortData->keyCount].offset *= (10);
                    sortData->key[sortData->keyCount].offset +=
                                                  (*lpszInstructions - '0');
                    lpszInstructions++;  /* '0' .. '9' */
                } while (*lpszInstructions != ',');
                (sortData->key[sortData->keyCount].offset)--;
                lpszInstructions++;      /* ',' */

                sortData->key[sortData->keyCount].length = 0;
                do {
                    sortData->key[sortData->keyCount].length *= (10);
                    sortData->key[sortData->keyCount].length +=
                                                  (*lpszInstructions - '0');
                    lpszInstructions++;  /* '0' .. '9' */
                } while (*lpszInstructions != ',');
                lpszInstructions++;      /* ',' */

                sortData->key[sortData->keyCount].type = *lpszInstructions;
                if (sortData->key[sortData->keyCount].type == 'W')
                    sortData->key[sortData->keyCount].type = 'C';
                lpszInstructions++;      /* 'E', 'N', 'C', or 'W' */
                lpszInstructions++;      /* ',' */

                if (*lpszInstructions == 'A')
                    sortData->key[sortData->keyCount].descending = FALSE;
                else
                    sortData->key[sortData->keyCount].descending = TRUE;
                lpszInstructions++;      /* 'A' or 'D' */

                (sortData->keyCount)++;
            
            } while (*lpszInstructions != ')');
            break;

        case 'F':
            
            /* Parse: F(FIX,recsize) */
            do {
                lpszInstructions++;      /* "F(FIX" */
            } while (*lpszInstructions != ',');
            lpszInstructions++;          /* ',' */

            do {
                sortData->recordSize *= (10);
                sortData->recordSize += (*lpszInstructions - '0');
                lpszInstructions++;      /* '0' .. '9' */
            } while (*lpszInstructions != ')');
            break;

        case 'W':

            /* Parse: W(workdir) */
            do {
                lpszInstructions++;      /* "W(workdir" */
            } while (*lpszInstructions != ')');
            break;

        case 'D':

            /* Parse: DUPO(Bbuffer,offset,length) */
            do {
                lpszInstructions++;      /* "DUPO(Bbuffer" */
            } while (*lpszInstructions != ',');
            lpszInstructions++;          /* ',' */
                
            do {
                sortData->dupKeyStart *= (10);
                sortData->dupKeyStart += (*lpszInstructions - '0');
                lpszInstructions++;      /* '0' .. '9' */
            } while (*lpszInstructions != ',');
            (sortData->dupKeyStart)--;
            lpszInstructions++;          /* ',' */

            do {
                sortData->dupKeyLen *= (10);
                sortData->dupKeyLen += (*lpszInstructions - '0');
                lpszInstructions++;      /* '0' .. '9' */
            } while (*lpszInstructions != ')');
            break;

        default:
            break;
        }
        lpszInstructions++;
    }
    sortData->recordStart = 0;
    sortData->recordCount = 0;
    sortData->bufferRecordCount = 0;
    sortData->dataBuffer = NULL;
    return ERR_SUCCESS;
}

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

BOOL INTFUNC QuickSortKeyCompare(SORT_DATA FAR *sortData, UWORD left,
                                     UWORD right)

/* Is the key of the left greater than or equal the key of the right? */

{
    LPSTR leftRecordPtr;
    LPSTR rightRecordPtr;

    /* Handle boundry conditions */
    if (left == sortData->recordCount + 1)
        return TRUE;
    if (right == 0)
        return TRUE;
    if (left == 0)
        return FALSE;
    if (right == sortData->recordCount + 1)
        return FALSE;

    /* Point at the records */
    leftRecordPtr = &(sortData->dataBuffer[left * sortData->recordSize]);
    rightRecordPtr = &(sortData->dataBuffer[right * sortData->recordSize]);

    /* Compare the keys */
    return SortKeyCompare(sortData, leftRecordPtr, rightRecordPtr);
}
/***************************************************************************/

RETCODE INTFUNC SortFile(HFILE hfileIn, HFILE hfileOut,
                         SORT_DATA FAR *sortData)

/* Sort one bunch of records.  A 'bunch' contains records n to (n + m), */
/* where n is specified by sortData->recordStart, m is specified by */
/* sortData->recordCount */

{
    UWORD leftBound;
    UWORD rightBound;
    UWORD entryToPutInPosition;
    LONG filesize;
    HGLOBAL hSections;
    SORT_SECTIONS sections;
    UWORD sectionsCount;

    /* Read records to sort */
    if (_llseek(hfileIn, ((UDWORD) sortData->recordStart) *
                        ((UDWORD) sortData->recordSize), 0) == HFILE_ERROR) {
        return ERR_SORT;
    }
    filesize = sortData->recordCount * sortData->recordSize;
    if (_lread(hfileIn, &(sortData->dataBuffer[sortData->recordSize]),
                                                   (UINT) filesize)
                  != (UINT) filesize) {
        return ERR_SORT;
    }

    /* Allocate space for list of sections to sort */
    sectionsCount = 0;
    hSections = GlobalAlloc(GMEM_MOVEABLE,
                         sizeof(SORT_SECTION) * sortData->bufferRecordCount);
    if (hSections == NULL) {
        return ERR_SORT;
    }
    sections = (SORT_SECTIONS) GlobalLock(hSections);
    if (sections == NULL) {
        GlobalFree(hSections);
        return ERR_SORT;
    }

    /* Initialize list of sections to sort to contain the entire bunch */
    /* provided */
    sections[0].left = 1;
    sections[0].right = sortData->recordCount;
    if (sections[0].right > sections[0].left)
        (sectionsCount)++;

    /* While there are still sections of the buffer to sort... */
    while (sectionsCount > 0) {

        /* Remove the definition of this section off the list */
        (sectionsCount)--;

        /* (Q2) We are going to look for the ultimate location of the */
        /* left-most element in the section.  Call the left-most entry */
        /* the "entry to put in position".  Point the left and right */
        /* bounds to everything except the entry to put in position. */
        /* While we are looking for this ultimate location, we will swap */
        /* records so that all the entries to the left of the ultimate */
        /* location are less than or equal to the entry to put in position */
        /* and all the entries to the right of the ultimate location are */
        /* greater than or equal to the entry to put in position. */
        entryToPutInPosition = sections[sectionsCount].left;
        leftBound = sections[sectionsCount].left;
        rightBound = sections[sectionsCount].right + 1;
        while (TRUE) {

            /* (Q3) Skip over entries on the left side that are less than */
            /* the entry to put in position */
            while (TRUE) {
                leftBound++;
                if (QuickSortKeyCompare(sortData, leftBound,
                                                  entryToPutInPosition))
                    break;
            }

            /* (Q4) Skip over entries on the right side that are greater */
            /* than the entry to put in position */
            while (TRUE) {
                rightBound--;
                if (QuickSortKeyCompare(sortData, entryToPutInPosition,
                                          rightBound))
                    break;
            }

            /* (Q5) Leave this loop if we have found the ultimate location */
            /* for the entry to put in position */
            if (rightBound <= leftBound)
                break;

            /* (Q6) At this point, the entry at the right bound is less */
            /* than or equal to the entry to put in position and the */
            /* entry at the left bound is greater than or equal to the */
            /* entry to put in position.  Swap these two entries and keep */
            /* looking for ultimate place for the entry to put in position */
            _fmemcpy(sortData->dataBuffer,
                   &(sortData->dataBuffer[leftBound * sortData->recordSize]),
                   sortData->recordSize);
            _fmemcpy(&(sortData->dataBuffer[leftBound * sortData->recordSize]),
                   &(sortData->dataBuffer[rightBound * sortData->recordSize]),
                   sortData->recordSize);
            _fmemcpy(&(sortData->dataBuffer[rightBound * sortData->recordSize]),
                   sortData->dataBuffer,
                   sortData->recordSize);
        }

        /* (Q5) Put the entry to put in position into its ultimate location */
        if (entryToPutInPosition != rightBound) {
            _fmemcpy(sortData->dataBuffer,
                 &(sortData->dataBuffer[entryToPutInPosition * sortData->recordSize]),
                 sortData->recordSize);
            _fmemcpy(&(sortData->dataBuffer[entryToPutInPosition * sortData->recordSize]),
                 &(sortData->dataBuffer[rightBound * sortData->recordSize]),
                 sortData->recordSize);
            _fmemcpy(&(sortData->dataBuffer[rightBound * sortData->recordSize]),
                 sortData->dataBuffer,
                 sortData->recordSize);
        }

        /* (Q8) Put the right half of the section onto the list of sections */ 
        /* to sort (it will sorted eventually) */
        sections[sectionsCount].left = rightBound+1;
        /* sections[sectionsCount].right is already set from before */
        if (sections[sectionsCount].right > sections[sectionsCount].left)
            (sectionsCount)++;

        /* (Q7) Put the left half of the section onto the list of sections */
        /* to sort (it will sorted next) */
        sections[sectionsCount].left = entryToPutInPosition;
        sections[sectionsCount].right = rightBound-1;
        if (sections[sectionsCount].right > sections[sectionsCount].left)
            (sectionsCount)++;
    }
    GlobalUnlock(hSections);
    GlobalFree(hSections);

    /* Write the sorted records to the file */
    if (_llseek(hfileOut, ((UDWORD) sortData->recordStart) *
                        ((UDWORD) sortData->recordSize), 0) == HFILE_ERROR) {
        return ERR_SORT;
    }
    if (_lwrite(hfileOut, &(sortData->dataBuffer[sortData->recordSize]),
                                                   (UINT) filesize)
                  != (UINT) filesize) {
        return ERR_SORT;
    }

    return ERR_SUCCESS;
}
/***************************************************************************/

RETCODE INTFUNC MergeFile(HFILE hfile, SORT_DATA FAR *sortData,
                          UDWORD recordCount)

/* Merge the sorted bunches in the file.  To do this, we merge the last */
/* bunch (the "Beta" records) with the second to last bunch (the "Alpha" */
/* records).  We then merge the last two bunches (the "Beta" records) with */
/* the third to last bunch (the "Alpha" records), etc. until the entire */
/* file is merged. */

{
    UDWORD bunchCount;
    UWORD bunchSize;
    UWORD lastBunchSize;
    HGLOBAL hAlphaBuffer;
    LPSTR alphaBuffer;
    UDWORD currentAlphaBunch;
    LPSTR alphaRecord;
    UWORD alphaCounter;
    HGLOBAL hBetaBuffer;
    LPSTR betaBuffer;
    UDWORD nextBetaBunch;
    LPSTR betaRecord;
    UWORD betaCounter;
    UDWORD nextOutputBunch;
    LPSTR outputRecord;
    UWORD outputCounter;

    /* Return if nothing to merge */
    if ((UDWORD) sortData->bufferRecordCount == recordCount)
        return ERR_SUCCESS;

    /* Figure out how many bunches there are */
    bunchCount = ((recordCount - 1) / sortData->bufferRecordCount) + 1;

    /* Figure out the size of a bunch */
    bunchSize = sortData->bufferRecordCount;

    /* Figure out how big last bunch is */
    lastBunchSize = (UWORD) (recordCount - ((bunchCount - 1) * bunchSize));

    /* Get a buffer to read records.  This buffer will be used for the */
    /* "Alpha" records (th second to last bunch on the first iteration, */
    /* the third to last bunch on the second iteration, etc.) */
    hAlphaBuffer = GlobalAlloc(GMEM_MOVEABLE,
                                   sortData->recordSize * bunchSize);
    if (hAlphaBuffer == NULL) {
        return ERR_SORT;
    }
    alphaBuffer = (LPSTR) GlobalLock(hAlphaBuffer);
    if (alphaBuffer == NULL) {
        GlobalFree(hAlphaBuffer);
        return ERR_SORT;
    }

    /* Get another buffer to read records.  This buffer will be used for */
    /* the "Beta" records (the last bunch on the first iteration, the */
    /* second to last and last bunch on the second iteration, etc.) */
    hBetaBuffer = GlobalAlloc(GMEM_MOVEABLE, sortData->recordSize*bunchSize);
    if (hBetaBuffer == NULL) {
        GlobalUnlock(hAlphaBuffer);
        GlobalFree(hAlphaBuffer);
        return ERR_SORT;
    }
    betaBuffer = (LPSTR) GlobalLock(hBetaBuffer);
    if (betaBuffer == NULL) {
        GlobalFree(hBetaBuffer);
        GlobalUnlock(hAlphaBuffer);
        GlobalFree(hAlphaBuffer);
        return ERR_SORT;
    }

    /* Point at the first alpha bunch */
    currentAlphaBunch = bunchCount - 1;

    /* Do the merge. */
    do {

        /* Read the next alpha bunch */
        if (_llseek(hfile, ((UDWORD) (currentAlphaBunch - 1)) *
                           ((UDWORD) (sortData->recordSize * bunchSize)),
                                                         0) == HFILE_ERROR) {
            GlobalUnlock(hBetaBuffer);
            GlobalFree(hBetaBuffer);
            GlobalUnlock(hAlphaBuffer);
            GlobalFree(hAlphaBuffer);
            return ERR_SORT;
        }
        alphaRecord = alphaBuffer;
        alphaCounter = bunchSize;
        if (_lread(hfile, alphaBuffer,
                   (UINT) (sortData->recordSize * alphaCounter)) !=
                             (UINT) (sortData->recordSize * alphaCounter)) {
            GlobalUnlock(hBetaBuffer);
            GlobalFree(hBetaBuffer);
            GlobalUnlock(hAlphaBuffer);
            GlobalFree(hAlphaBuffer);
            return ERR_SORT;
        }

        /* Point at the output buffer */
        nextOutputBunch = currentAlphaBunch;
        outputRecord = sortData->dataBuffer;
        outputCounter = 0;

        /* Merge the alpha records with the beta records */
        nextBetaBunch = currentAlphaBunch + 1;
        betaCounter = 0;
        while (alphaCounter > 0) {

            /* Do we need more beta records? */
            if (betaCounter == 0) {

                /* Yes.  Leave loop if no more records. */
                if (nextBetaBunch > bunchCount)
                    break;

                /* Position to the next bunch of beta records */
                if (_llseek(hfile, ((UDWORD) (nextBetaBunch - 1)) *
                               ((UDWORD) (sortData->recordSize * bunchSize)),
                                                     0) == HFILE_ERROR) {
                    GlobalUnlock(hBetaBuffer);
                    GlobalFree(hBetaBuffer);
                    GlobalUnlock(hAlphaBuffer);
                    GlobalFree(hAlphaBuffer);
                    return ERR_SORT;
                }

                /* Read in the next bunch of beta records */
                betaRecord = betaBuffer;
                if (nextBetaBunch != bunchCount)
                    betaCounter = bunchSize;
                else
                    betaCounter = lastBunchSize;
                if (_lread(hfile, betaBuffer,
                           (UINT) (sortData->recordSize * betaCounter)) !=
                             (UINT) (sortData->recordSize * betaCounter)) {
                    GlobalUnlock(hBetaBuffer);
                    GlobalFree(hBetaBuffer);
                    GlobalUnlock(hAlphaBuffer);
                    GlobalFree(hAlphaBuffer);
                    return ERR_SORT;
                }

                nextBetaBunch++;
            }

            /* If the output buffer is full, write it out */
            if (outputCounter == bunchSize) {

                if (_llseek(hfile, ((UDWORD) (nextOutputBunch - 1)) *
                             ((UDWORD) (sortData->recordSize * bunchSize)),
                                                     0) == HFILE_ERROR) {
                    GlobalUnlock(hBetaBuffer);
                    GlobalFree(hBetaBuffer);
                    GlobalUnlock(hAlphaBuffer);
                    GlobalFree(hAlphaBuffer);
                    return ERR_SORT;
                }

                if (_lwrite(hfile, sortData->dataBuffer,
                       (UINT) (sortData->recordSize * outputCounter)) !=
                             (UINT) (sortData->recordSize * outputCounter)) {
                    GlobalUnlock(hBetaBuffer);
                    GlobalFree(hBetaBuffer);
                    GlobalUnlock(hAlphaBuffer);
                    GlobalFree(hAlphaBuffer);
                    return ERR_SORT;
                }

                /* Point at next output buffer */
                nextOutputBunch++;
                outputRecord = sortData->dataBuffer;
                outputCounter = 0;
            }

            /* Which record is next, the one in the alpha buffer or the */
            /* one in the beta buffer */
            if (SortKeyCompare(sortData, betaRecord, alphaRecord)) {

                /* The one in the alpha buffer.  Put it into output buffer */
                _fmemcpy(outputRecord, alphaRecord, sortData->recordSize);
                
                /* Point at next alpha record */
                alphaCounter--;
                alphaRecord += sortData->recordSize;
            }
            else {

                /* The one in the beta buffer.  Put it into output buffer */
                _fmemcpy(outputRecord, betaRecord, sortData->recordSize);

                /* Point at next beta record */
                betaCounter--;
                betaRecord += sortData->recordSize;
            }

            /* Point at next output record */
            outputCounter++;
            outputRecord += sortData->recordSize;
        }

        /* Write the remining records in the output buffer */
        
        if (_llseek(hfile, ((UDWORD) (nextOutputBunch - 1)) *
                           ((UDWORD) (sortData->recordSize * bunchSize)),
                                                    0) == HFILE_ERROR) {
            GlobalUnlock(hBetaBuffer);
            GlobalFree(hBetaBuffer);
            GlobalUnlock(hAlphaBuffer);
            GlobalFree(hAlphaBuffer);
            return ERR_SORT;
        }

		if (outputCounter > 0) {

            if (_lwrite(hfile, sortData->dataBuffer,
                       (UINT) (sortData->recordSize * outputCounter)) !=
                             (UINT) (sortData->recordSize * outputCounter)) {
                GlobalUnlock(hBetaBuffer);
                GlobalFree(hBetaBuffer);
                GlobalUnlock(hAlphaBuffer);
                GlobalFree(hAlphaBuffer);
                return ERR_SORT;
            }
        }

        /* Any more records in the beta buffer? */
        if (betaCounter > 0) {

            if (_lwrite(hfile, betaRecord,
                       (UINT) (sortData->recordSize * betaCounter)) !=
                             (UINT) (sortData->recordSize * betaCounter)) {
                GlobalUnlock(hBetaBuffer);
                GlobalFree(hBetaBuffer);
                GlobalUnlock(hAlphaBuffer);
                GlobalFree(hAlphaBuffer);
                return ERR_SORT;
            }
        }

        /* Any more records in the alpha buffer? */
        if (alphaCounter > 0) {

            if (_lwrite(hfile, alphaRecord,
                       (UINT) (sortData->recordSize * alphaCounter)) !=
                             (UINT) (sortData->recordSize * alphaCounter)) {
                GlobalUnlock(hBetaBuffer);
                GlobalFree(hBetaBuffer);
                GlobalUnlock(hAlphaBuffer);
                GlobalFree(hAlphaBuffer);
                return ERR_SORT;
            }
        }

        /* Do the next bunch */
        currentAlphaBunch--;

    } while (currentAlphaBunch != 0);

    /* Clean up */
    GlobalUnlock(hBetaBuffer);
    GlobalFree(hBetaBuffer);
    GlobalUnlock(hAlphaBuffer);
    GlobalFree(hAlphaBuffer);

    return ERR_SUCCESS;
}
/***************************************************************************/

RETCODE INTFUNC RemoveDups(HFILE hfileIn, HFILE hfileOut,
                       SORT_DATA FAR *sortData, UDWORD FAR *lpRecordCount)

/* Removes the duplicate records.  The number of input records is */
/* specified by *lpRecordCount, the number of resultant records is */
/* returned in *lpRecordCount also */
{
    LONG filesize;
    UWORD idx;
    LPSTR prevRecordPtr;
    LPSTR thisRecordPtr;
    LPSTR prevKeyPtr;
    LPSTR thisKeyPtr;
    UDWORD recordsRemaining;
    BOOL firstTime;

    /* Position at the start of the files */
    if (_llseek(hfileIn, 0, 0) == HFILE_ERROR) {
        return ERR_SORT;
    }
    if (_llseek(hfileOut, 0, 0) == HFILE_ERROR) {
        return ERR_SORT;
    }

    /* While there are still some records... */
    firstTime = TRUE;
    recordsRemaining = *lpRecordCount;
    while (recordsRemaining > 0) {

        /* Find out how many records to look through this iteration */
        if (recordsRemaining <= sortData->bufferRecordCount)
            sortData->recordCount = (UWORD) recordsRemaining;
        else
            sortData->recordCount = sortData->bufferRecordCount;
        recordsRemaining -= (sortData->recordCount);

        /* If this is the first iteration, read the first bunch of records */
        /* into the very beginning of the buffer.  Otherwise, read them */
        /* into the next record position in order to preserve the first */
        /* record in the buffer. */
        filesize = sortData->recordCount * sortData->recordSize;
        if (firstTime) {
            if (_lread(hfileIn, sortData->dataBuffer, (UINT) filesize) !=
                                                        (UINT) filesize) {
                return ERR_SORT;
            }

            /* If this is the first iteration, remove the record in the */
            /* first position from the count (it will be added back in */
            /* when we do the last bunch) */
            (sortData->recordCount)--;
            firstTime = FALSE;
        }
        else {
            if (_lread(hfileIn, &(sortData->dataBuffer[sortData->recordSize]),
                                    (UINT) filesize) != (UINT) filesize) {
                return ERR_SORT;
            }
        }
        
        /* Point at the records to process */
        prevRecordPtr = sortData->dataBuffer;
        prevKeyPtr = prevRecordPtr + sortData->dupKeyStart;
        thisRecordPtr = prevRecordPtr + sortData->recordSize;
        thisKeyPtr = prevKeyPtr + sortData->recordSize;

        /* For each record... */
        for (idx = 1; idx < sortData->recordCount+1; idx++) {

            /* Is the record a duplicate of the previous one? */
            if (_fmemcmp(prevKeyPtr, thisKeyPtr, sortData->dupKeyLen) == 0) {

                /* Yes.  Remove it */
                _fmemcpy(prevRecordPtr, thisRecordPtr,
                   sortData->recordSize * (sortData->recordCount - idx + 1));

                /* Reduce record count */
                (*lpRecordCount)--;
                sortData->recordCount--;
                idx--;
            }
            else {

                /* No.  Point at next record */
                prevRecordPtr = thisRecordPtr;
                thisRecordPtr += (sortData->recordSize);
                prevKeyPtr = thisKeyPtr;
                thisKeyPtr += (sortData->recordSize);
            }
        }

        /* If there are no more records, write all the records in the */
        /* buffer (this is where we add back the record removed from the */
        /* first bunch) */
        if (recordsRemaining == 0)
            (sortData->recordCount)++;

        /* Are there any records to write? */
        if (sortData->recordCount > 0) {

            /* Yes.  Write them out */
            filesize = (sortData->recordCount) * sortData->recordSize;
            if (_lwrite(hfileOut, sortData->dataBuffer, (UINT) filesize) !=
                                            (UINT) filesize) {
                return ERR_SORT;
            }
        }

        /* Move the last record to the beginning of the buffer for the */
        /* next iteration */
        if (recordsRemaining != 0) {
            _fmemcpy(sortData->dataBuffer, prevRecordPtr,
                                                 sortData->recordSize);
        }
    }

    /* Set the end of file mark in the output file */
    if (_lwrite(hfileOut, sortData->dataBuffer, 0) != 0) {
        return ERR_SORT;
    }

    return ERR_SUCCESS;
}
/***************************************************************************/
#ifdef WIN32
void s_1mains(char * szFilenameIn, char * szFilenameOut,
              char * lpszInstructions, long * lpRecordCount, int * lpErrcode)
#else
void far PASCAL s_1mains(LPSTR szFilenameIn, LPSTR szFilenameOut,
              LPSTR lpszInstructions, LPLONG lpRecordCount, LPINT lpErrcode)
#endif
{
    LONG filesize;
    HGLOBAL hDataBuffer;
    UDWORD recordCount;
    UDWORD recordsRemaining;
    SORT_DATA sortData;
    HFILE hfileIn;
    HFILE hfileOut;

    /* Get sorting instructions */
    *lpErrcode = SortInstructions(lpszInstructions, &sortData);
    if (*lpErrcode != ERR_SUCCESS)
        return;

    /* Open input file */
    hfileIn = _lopen(szFilenameIn, OF_READWRITE);
    if (hfileIn == HFILE_ERROR) {
        *lpErrcode = ERR_SORT;
        return;
    }

    /* Figure out how many records there are */
    filesize = _llseek(hfileIn, 0, 2);
    if (filesize == HFILE_ERROR) {
        _lclose(hfileIn);
        *lpErrcode = ERR_SORT;
        return;
    }
    recordCount = ((UDWORD) filesize) / sortData.recordSize;

    /* Error if any incomplete records */
    if ((recordCount * sortData.recordSize) != (UDWORD) filesize) {
        _lclose(hfileIn);
        *lpErrcode = ERR_SORT;
        return;
    }

    /* Get buffer to read records into */
    if (filesize > (65536 - sortData.recordSize))
        sortData.bufferRecordCount = (UWORD) ((65536 - sortData.recordSize) /
                                                     sortData.recordSize);
    else
        sortData.bufferRecordCount = (UWORD) recordCount;
    hDataBuffer = GlobalAlloc(GMEM_MOVEABLE,
                     sortData.recordSize * (sortData.bufferRecordCount+1));
    if (hDataBuffer == NULL) {
        _lclose(hfileIn);
        *lpErrcode = ERR_SORT;
        return;
    }
    sortData.dataBuffer = (LPSTR) GlobalLock(hDataBuffer);
    if (sortData.dataBuffer == NULL) {
        GlobalFree(hDataBuffer);
        _lclose(hfileIn);
        *lpErrcode = ERR_SORT;
        return;
    }

    /* Open output file */
    hfileOut = _lopen(szFilenameOut, OF_READWRITE);
    if (hfileOut == HFILE_ERROR) {
        _lclose(hfileIn);
        *lpErrcode = ERR_SORT;
        return;
    }

    /* Sort the file (sortData.bufferRecordCount records at a time) */
    *lpErrcode = ERR_SUCCESS;
    sortData.recordStart = 0;
    recordsRemaining = recordCount;
    while ((*lpErrcode == ERR_SUCCESS) && (recordsRemaining > 0)) {
        
        /* Find out how many records in the next bunch to sort */
        if (recordsRemaining <= sortData.bufferRecordCount)
            sortData.recordCount = (UWORD) recordsRemaining;
        else
            sortData.recordCount = sortData.bufferRecordCount;
        recordsRemaining -= (sortData.recordCount);

        /* Sort the next bunch of records */
        if (sortData.dupKeyLen == 0)
            *lpErrcode = SortFile(hfileIn, hfileOut, &sortData);
        else
            *lpErrcode = SortFile(hfileIn, hfileIn, &sortData);

        /* Point at next bunch of records */
        sortData.recordStart += (sortData.recordCount);
    }

    /* Merge the file */
    if (*lpErrcode == ERR_SUCCESS) {
        if (sortData.dupKeyLen == 0)
            *lpErrcode = MergeFile(hfileOut, &sortData, recordCount);
        else
            *lpErrcode = MergeFile(hfileIn, &sortData, recordCount);
    }

    /* Remove the duplicates */
    if ((*lpErrcode == ERR_SUCCESS) && (sortData.dupKeyLen != 0))
        *lpErrcode = RemoveDups(hfileIn, hfileOut, &sortData,
                                &recordCount);

    /* Clean up */
    _lclose(hfileOut);
    GlobalUnlock(hDataBuffer);
    GlobalFree(hDataBuffer);
    _lclose(hfileIn);

    /* Return final record count */
    *lpRecordCount = (LONG) recordCount;
    return;
}
/***************************************************************************/