/*
 * utils.c
 *
 *
 * some standard file-reading, hashing and checksum routines.

 *
 * Geraint Davies, July 92
 */

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <winnls.h>

#include "gutils.h"
#include "gutilsrc.h"


const WCHAR c_wchMagic = 0xfeff;        // magic marker for Unicode files


/*
 * we need an instance handle. this should be the dll instance
 */
extern HANDLE hLibInst;

/*
 * -- forward declaration of procedures -----------------------------------
 */
INT_PTR dodlg_stringin(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);

/*-- readfile: buffered line input ------------------------------*/

/*
 * set of functions to read a line at a time from a file, using
 * a buffer to read a block at a time from the file
 *
 */

/*
 * a FILEBUFFER handle is a pointer to a struct filebuffer
 */
struct filebuffer {
    int fh;         /* open file handle */
    LPSTR start;    /* offset within buffer of next character */
    LPSTR last;     /* offset within buffer of last valid char read in */

    char buffer[BUFFER_SIZE];

    BOOL fUnicode;  /* TRUE if the file is Unicode */
    WCHAR wzBuffer[MAX_LINE_LENGTH];
    LPWSTR pwzStart;
    LPWSTR pwzLast;
};

typedef enum {
    CT_LEAD = 0,
    CT_TRAIL = 1,
    CT_ANK = 2,
    CT_INVALID = 3,
} DBCSTYPE;

DBCSTYPE
DBCScharType(
            LPTSTR str,
            int index
            )
{
    /*
        TT .. ??? maybe LEAD or TRAIL
        FT .. second == LEAD
        FF .. second == ANK
        TF .. ??? maybe ANK or TRAIL
    */
    // (chrisant) this was really broken to use lstrlen here; readfile_next
    // uses this on fbuf->buffer which is explicitly NOT null-terminated.
    if ( index >= 0 /*|| index <= lstrlen(str)*/ ) {   //  EOS is valid parameter.
        LPTSTR pos = str + index;
        DBCSTYPE candidate = (IsDBCSLeadByte( *pos-- ) ? CT_LEAD : CT_ANK);
        BOOL maybeTrail = FALSE;
        for ( ; pos >= str; pos-- ) {
            if ( !IsDBCSLeadByte( *pos ) )
                break;
            maybeTrail ^= 1;
        }
        return maybeTrail ? CT_TRAIL : candidate;
    }
    return CT_INVALID;
}

/*
 * initialise a filebuffer and return a handle to it
 */
FILEBUFFER
APIENTRY
readfile_new(
            int fh,
            BOOL *pfUnicode
            )
{
    FILEBUFFER fbuf;
    UINT cbRead;
    WCHAR wchMagic;

    *pfUnicode = FALSE;

    fbuf = (FILEBUFFER) GlobalLock(GlobalAlloc(LHND, sizeof(struct filebuffer)));
    if (fbuf == NULL) {
        return(NULL);
    }

    fbuf->fh = fh;
    fbuf->start = fbuf->buffer;
    fbuf->last = fbuf->buffer;
    fbuf->fUnicode = FALSE;
    /* return file pointer to beginning of file */
    _llseek(fh, 0, 0);

    cbRead = _lread(fh, &wchMagic, sizeof(wchMagic));
    if (cbRead == 2 && c_wchMagic == wchMagic)
    {
        fbuf->fUnicode = TRUE;
        *pfUnicode = TRUE;
        fbuf->pwzStart = fbuf->wzBuffer;
        fbuf->pwzLast = fbuf->wzBuffer;
    }
    else
    {
        _llseek(fh, 0, 0);
    }

    return(fbuf);
}

/* delims is the set of delimiters used to break lines
 * For program source files the delimiter is \n.
 * Full stop (aka period) i.e. "." is another obvious one.
 * The delimiters are taken as
 * being part of the line they terminate.
 *
 * The current strategy will NOT port to UNICODE easily!  It relies on having a
 * character set for which we can easily allocate one byte per character in the set.
 *
 * The model is that it only makes sense to have one set of delimiters on the go.
 * If we allow different delimiters for each file then we could make delims a field
 * in a struct filebuffer.
 */
static BYTE delims[256];

/* set str to be the set of delims.  str is a \0 delimited string */
void
APIENTRY
readfile_setdelims(
                  LPBYTE str
                  )
{
    /* clear all bytes of delims */
    int i;
    for (i=0; i<256; ++i) {
        delims[i] = 0;
    }

    /* set the bytes in delims which correspond to delimiters */
    for (; *str; ++str) {delims[(int)(*str)] = 1;
    }

} /* readfile_setdelims */


static BOOL FFindEOL(FILEBUFFER fbuf, LPSTR *ppszLine, int *pcch, LPWSTR *ppwzLine, int *pcwch)
{
    LPSTR psz;
    LPWSTR pwz;

    if (fbuf->fUnicode)
    {
        for (pwz = fbuf->pwzStart; pwz < fbuf->pwzLast; pwz++)
        {
            if (!*pwz)
                *pwz = '.';

            //$ review: (chrisant) not strictly correct, but easiest for now
            // to get unicode up and limping.
            if (*pwz < 256 && delims[*pwz])
            {
                *pcwch = (UINT)(pwz - fbuf->pwzStart) + 1;
                *ppwzLine = fbuf->pwzStart;
                fbuf->pwzStart += *pcwch;
                // notice we fall thru and let the loop below actually return
                break;
            }
        }
    }
    for (psz = fbuf->start; psz < fbuf->last; psz = CharNext(psz))
    {
        if (!*psz)
            *psz = '.';

        if (delims[*psz])
        {
            *pcch = (UINT)(psz - fbuf->start) + 1;
            *ppszLine = fbuf->start;
            fbuf->start += *pcch;
            return TRUE;
        }
    }
    return FALSE;
}


/*
 * get the next line from a file. returns a pointer to the line
 * in the buffer - so copy it before changing it.
 *
 * the line is *not* null-terminated. *plen is set to the length of the
 * line.
 *
 * A line is terminated by any character in the static var set delims.
 */
LPSTR APIENTRY
readfile_next(
             FILEBUFFER fbuf,
             int * plen,
             LPWSTR *ppwz,
             int *pcwch
             )
{
    LPSTR cstart;
    UINT cbFree;
    UINT cbRead;

    //$ FUTURE: (chrisant) THIS DOES NOT HANDLE UNICODE 3.0 SURROGATE PAIRS
    // CORRECTLY YET.

    *ppwz = NULL;
    *pcwch = 0;

    /* look for an end of line in the buffer we have */
    if (FFindEOL(fbuf, &cstart, plen, ppwz, pcwch))
    {
        return cstart;
    }

    /* no delimiter in this buffer - this buffer contains a partial line.
     * copy the partial up to the beginning of the buffer, and
     * adjust the pointers to reflect this move
     */
    if (fbuf->fUnicode)
    {
        memmove(fbuf->wzBuffer, fbuf->pwzStart, (LPBYTE)fbuf->pwzLast - (LPBYTE)fbuf->pwzStart);
        fbuf->pwzLast = fbuf->wzBuffer + (fbuf->pwzLast - fbuf->pwzStart);
        fbuf->pwzStart = fbuf->wzBuffer;
    }
    memmove(fbuf->buffer, fbuf->start, (LPBYTE)fbuf->last - (LPBYTE)fbuf->start);
    fbuf->last = fbuf->buffer + (fbuf->last - fbuf->start);
    fbuf->start = fbuf->buffer;

    /* read in to fill the block */
    if (fbuf->fUnicode)
    {
        // HACK: for unicode files, we'll read in the unicode and convert it
        // to ansi.  we try to be clever by converting to ACP, then converting
        // back to unicode, and comparing the two unicode strings.  for any
        // wchars that are not identical, we replace them with 5-byte hex
        // codes of the format xFFFF.
        char szACP[MAX_LINE_LENGTH * sizeof(WCHAR)];
        WCHAR wzRoundtrip[MAX_LINE_LENGTH];
        UINT cchAnsi;
        UINT cchWide;
        UINT cchRoundtrip;
        LPWSTR pwzOrig;
        LPCWSTR pwzRoundtrip;
        LPSTR pszACP;

        cbFree = sizeof(fbuf->wzBuffer) - (UINT)((LPBYTE)fbuf->pwzLast - (LPBYTE)fbuf->pwzStart);
        cbRead = _lread(fbuf->fh, fbuf->pwzLast, cbFree);
        //$ FUTURE: (chrisant) what if we read an odd number of bytes?  how
        // will that impact the _llseek(... -1 ...) calls near the bottom of
        // this function?

        // wide to ansi
        cchWide = cbRead / 2;
        cchAnsi = WideCharToMultiByte(GetACP(),
                                      0,
                                      fbuf->pwzLast,
                                      cchWide,
                                      szACP,
                                      DimensionOf(szACP),
                                      NULL,
                                      NULL);

        // round trip, to find chars not in ACP
        cchRoundtrip = MultiByteToWideChar(GetACP(),
                                           0,
                                           szACP,
                                           cchAnsi,
                                           wzRoundtrip,
                                           DimensionOf(wzRoundtrip));

        // find non-ACP chars
        pwzOrig = fbuf->pwzLast;
        pwzRoundtrip = wzRoundtrip;
        pszACP = szACP;
        while (cchWide && cchRoundtrip)
        {
            if (*pwzOrig == *pwzRoundtrip)
            {
                // copy the DBCS representation into the buffer
                if (IsDBCSLeadByte(*pszACP))
                    *(fbuf->last++) = *(pszACP++);
                *(fbuf->last++) = *(pszACP++);
            }
            else
            {
                // copy a hexized representation into the buffer
                static const char rgHex[] = "0123456789ABCDEF";
                *(fbuf->last++) = 'x';
                *(fbuf->last++) = rgHex[((*pwzOrig) >> 12) & 0xf];
                *(fbuf->last++) = rgHex[((*pwzOrig) >>  8) & 0xf];
                *(fbuf->last++) = rgHex[((*pwzOrig) >>  4) & 0xf];
                *(fbuf->last++) = rgHex[((*pwzOrig) >>  0) & 0xf];
                if (IsDBCSLeadByte(*pszACP))
                    pszACP++;
                pszACP++;
            }

            ++pwzOrig;
            ++pwzRoundtrip;
            --cchWide;
            --cchRoundtrip;
        }
		fbuf->pwzLast = pwzOrig;
    }
    else
    {
        cbFree = sizeof(fbuf->buffer) - (UINT)((LPBYTE)fbuf->last - (LPBYTE)fbuf->start);
        cbRead = _lread(fbuf->fh, fbuf->last, cbFree);
        if (cbRead == HFILE_ERROR)
        {
            cbRead = 0;
        }
        else if (DBCScharType(fbuf->last, cbRead-1) == CT_LEAD)
        {
            cbRead--;
            *(fbuf->last + cbRead) = '\0';
            _llseek(fbuf->fh,-1,FILE_CURRENT);
        }

        fbuf->last += cbRead;
    }

    /* look for an end of line in the newly filled buffer */
    if (FFindEOL(fbuf, &cstart, plen, ppwz, pcwch))
    {
        return cstart;
    }

    /* still no end of line. either the buffer is empty -
     * because of end of file - or the line is longer than
     * the buffer. in either case, return all that we have
     */

    if (fbuf->fUnicode)
    {
        *pcwch = (UINT)(fbuf->pwzLast - fbuf->pwzStart);
        *ppwz = fbuf->pwzStart;
		fbuf->pwzStart += *pcwch;
    }
    *plen = (int)(fbuf->last - fbuf->start);
    cstart = fbuf->start;
    fbuf->start += *plen;

    if (*plen == 0) {
        return(NULL);
    } else {
        return(cstart);
    }
}


/*
 * delete a FILEBUFFER -  free the buffer. We should NOT close the
 * handle at this point as we did not open it. the opener should close
 * it with a function that corresponds to however he opened it.
 */
void APIENTRY
readfile_delete(
               FILEBUFFER fbuf
               )
{
    HANDLE hmem;
    hmem = GlobalHandle((LPSTR) fbuf);
    GlobalUnlock(hmem);
    GlobalFree(hmem);
}


/* --- checksum ----------------------------------------------------  */

/*
 * Produce a checksum for a file:
 * Open a file, checksum it and close it again. err !=0 iff it failed.
 *
 * Overall scheme:
 *         Read in file in blocks of 8K (arbitrary number - probably
 *         beneficial if integral multiple of disk block size).
 *         Generate checksum by the formula
 *         checksum = SUM( rnd(i)*(dword[i]) )
 *         where dword[i] is the i-th dword in the file, the file being
 *         extended by up to three binary zeros if necessary.
 *         rnd(x) is the x-th element of a fixed series of pseudo-random
 *         numbers.
 *
 * You may notice that dwords that are zero do not contribute to the checksum.
 * This worried me at first, but it's OK.  So long as everything else DOES
 * contribute, the checksum still distinguishes between different files
 * of the same length whether they contain zeros or not.
 * An extra zero in the middle of a file will also cause all following non-zero
 * bytes to have different multipliers.  However the algorithm does NOT
 * distinguish between files which only differ in zeros at the end of the file.
 * Multiplying each dword by a pseudo-random function of its position
 * ensures that "anagrams" of each other come to different sums,
 * i.e. the file AAAABBBB will be different from BBBBAAAA.
 * The pseudorandom function chosen is successive powers of 1664525 modulo 2**32
 * 1664525 is a magic number taken from Donald Knuth's "The Art Of Computer Programming"
 *
 * The function appears to be compute bound.  Loop optimisation is appropriate!
 */
CHECKSUM
APIENTRY
checksum_file(
             LPCSTR fn,
             LONG * err
             )
{
    HFILE fh;
#define BUFFLEN 8192
    BYTE buffer[BUFFLEN];
    unsigned long lCheckSum = 0;         /* grows into the checksum */
    const unsigned long lSeed = 1664525; /* seed for random (Knuth) */
    unsigned long lRand = 1;             /* seed**n */
    unsigned Byte = 0;                   /* buffer[Byte] is next byte to process */
    unsigned Block = 0;                  /* number of bytes in buffer */
    BOOL Ending = FALSE;                 /* TRUE => binary zero padding added */
    int i;                               /* temp loop counter */

    *err = -2;                            /* default is "silly" */

    /* conceivably someone is fiddling with the file...?
       we give 6 goes, with delays of 1,2,3,4 and 5 secs between
    */
    for (i=0; i<=5; ++i) {
        Sleep(1000*i);
        fh = _lopen(fn, OF_READ|OF_SHARE_DENY_WRITE);
        if (fh!=HFILE_ERROR)
            break;

        {
            char msg[300];
            wsprintf( msg, "Windiff: retry open. Error(%d), file(%s)\n"
                      , GetLastError(), fn);
            OutputDebugString(msg);
        }
    }

    if (fh == HFILE_ERROR) {
        *err = GetLastError();
        return 0xFF00FF00 | GetCurrentTime();
        /* The odds are very strong that this will show up
           as a "Files Differ" value, whilst giving it a look
           that may be recogniseable to a human debugger!
        */
    }

    /* we assume that the file system will always give us the full length that
     * we ask for unless the end-of-file is encountered.
     * This means that for the bulk of a long file the buffer goes exactly into 4s
     * and only at the very end are some bytes left over.
     */

    for ( ; ;) {
        /* Invariant: (which holds at THIS point in the flow)
         * A every byte in every block already passed has contributed to the checksum
         * B every byte before buffer[byte] in current block has contributed
         * C Byte is a multiple of 4
         * D Block is a multiple of 4
         * E Byte <= Block
         * F Ending is TRUE iff zero padding has been added to any block so far.
         * G lRand is (lSeed to the power N) MOD (2 to the power 32)
         *   where N is the number of dwords in the file processed so far
         *   including both earlier blocks and the current block
         * To prove the loop good:
         * 1. Show invariant is initially true
         * 2. Show invariant is preserved by every loop iteration
         * 3. Show that IF the invariant is true at this point AND the program
         *    exits the loop, then the right answer will have been produced.
         * 4. Show the loop terminates.
         */

        if (Byte>=Block) {
            if (Byte>Block) {
                Trace_Error(NULL, "Checksum internal error.  Byte>Block", FALSE);
                *err = -1;
                break;                 /* go home */
            }
            Block = _lread(fh, (LPSTR)&(buffer), BUFFLEN);

            if (Block==HFILE_ERROR) {
                *err = GetLastError();
                break;            /* go home */
            }
            if (Block==0)
            /* ==0 is not error, but also no further addition to checksum */
            {
                /*
                 * Every byte has contributed, and there are no more
                 * bytes.  Checksum complete
                 */
                *err = 0;
                _lclose(fh);
                return lCheckSum;        /* success! */
            }

            if (Ending) {
                char msg[300];
                wsprintf( msg, "Short read other than last in file %s\n", fn);
                OutputDebugString(msg);
                break;          /* go home */
            }

            while (Block%4) {
                buffer[Block++] = 0;
                Ending = TRUE;
            }
            /* ASSERT the block now has a multiple of 4 bytes */
            Byte = 0;
        }
        lRand *= lSeed;
        lCheckSum += lRand* *((DWORD *)(&buffer[Byte]));
        Byte += 4;
    }
    _lclose(fh);
    return 0xFF00FF00 | GetCurrentTime();   /* See first "return" in function */
} /* checksum_file */





/* --- internal error popups ----------------------------------------*/

static BOOL sbUnattended = FALSE;

void
Trace_Unattended(
                BOOL bUnattended
                )
{
    sbUnattended = bUnattended;
} /* Trace_Unattended */


/* This function is called to report errors to the user.
 * if the current operation is abortable, this function will be
 * called with fCancel == TRUE and we display a cancel button. otherwise
 * there is just an OK button.
 *
 * We return TRUE if the user pressed OK, or FALSE otherwise (for cancel).
 */
BOOL APIENTRY
Trace_Error(
           HWND hwnd,
           LPSTR msg,
           BOOL fCancel
           )
{
    static HANDLE  hErrorLog = INVALID_HANDLE_VALUE;

    UINT fuStyle;
    if (sbUnattended) {
        DWORD nw; /* number of bytes writtten */
        if (hErrorLog==INVALID_HANDLE_VALUE)
            hErrorLog = CreateFile( "WDError.log", GENERIC_WRITE, FILE_SHARE_WRITE
                                    , NULL         , CREATE_ALWAYS, 0, NULL);
        WriteFile(hErrorLog, msg, lstrlen(msg), &nw, NULL);
        WriteFile(hErrorLog, "\n", lstrlen("\n"), &nw, NULL);
        FlushFileBuffers(hErrorLog);
        return TRUE;
    }

    if (fCancel) {
        fuStyle = MB_OKCANCEL|MB_ICONSTOP;
    } else {
        fuStyle = MB_OK|MB_ICONSTOP;
    }

    if (MessageBox(hwnd, msg, NULL, fuStyle) ==  IDOK) {
        return(TRUE);
    } else {
        return(FALSE);
    }
}

/* ------------ Tracing to a file ------------------------------------*/

static HANDLE  hTraceFile = INVALID_HANDLE_VALUE;

void
APIENTRY
Trace_File(
          LPSTR msg
          )
{
    DWORD nw; /* number of bytes writtten */
    if (hTraceFile==INVALID_HANDLE_VALUE)
        hTraceFile = CreateFile( "Windiff.trc"
                                 , GENERIC_WRITE
                                 , FILE_SHARE_WRITE
                                 , NULL
                                 , CREATE_ALWAYS
                                 , 0
                                 , NULL
                               );

    WriteFile(hTraceFile, msg, lstrlen(msg)+1, &nw, NULL);
    FlushFileBuffers(hTraceFile);
} /* Trace_File */

void
APIENTRY
Trace_Close(
           void
           )
{
    if (hTraceFile!=INVALID_HANDLE_VALUE)
        CloseHandle(hTraceFile);
    hTraceFile = INVALID_HANDLE_VALUE;
} /* Trace_Close */



/* ----------- things for strings-------------------------------------*/


/*
 * Compare two pathnames, and if not equal, decide which should come first.
 * Both path names should be lower cased by AnsiLowerBuff before calling.
 *
 * returns 0 if the same, -1 if left is first, and +1 if right is first.
 *
 * The comparison is such that all filenames in a directory come before any
 * file in a subdirectory of that directory.
 *
 * given direct\thisfile v. direct\subdir\thatfile, we take
 * thisfile < thatfile   even though it is second alphabetically.
 * We do this by picking out the shorter path
 * (fewer path elements), and comparing them up till the last element of that
 * path (in the example: compare the 'dir\' in both cases.)
 * If they are the same, then the name with more path elements is
 * in a subdirectory, and should come second.
 *
 * We have had trouble with apparently multiple collating sequences and
 * the position of \ in the sequence.  To eliminate this trouble
 * a. EVERYTHING is mapped to lower case first (actually this is done
 *    before calling this routine).
 * b. All comparison is done by using lstrcmpi with two special cases.
 *    1. Subdirs come after parents as noted above
 *    2. \ must compare low so that fred2\x > fred\x in the same way
 *       that fred2 < fred.  Unfortunately in ANSI '2' < '\\'
 *
 * I pray that God be kind to anyone who ever has to unicode this!
 *
 */
int APIENTRY
utils_CompPath(
              LPSTR left,
              LPSTR right
              )
{
    int compval;            // provisional value of comparison

    if (left==NULL) return -1;          // empty is less than anything else
    else if (right==NULL) return 1;           // anything is greater than empty

    for (; ; ) {
        if (*left=='\0' && *right=='\0') return 0;
        if (*left=='\0')  return -1;
        if (*right=='\0')  return 1;
        if (IsDBCSLeadByte(*left) || IsDBCSLeadByte(*right)) {
            if (*right != *left) {
                compval = (*left - *right);
                break;
            }
            ++left;
            ++right;
            if (*right != *left) {
                compval = (*left - *right);
                break;
            }
            ++left;
            ++right;
        } else {
            if (*right==*left) {++left; ++right; continue;}
            if (*left=='\\') {compval = -1; break;}
            if (*right=='\\') {compval = 1; break;}
            compval = (*left - *right);
            break;
        }
    }

    /* We have detected a difference.  If the rest of one
       of the strings (including the current character) contains
       some \ characters, but the other one does not, then all
       elements up to the last element of the one with the fewer
       elements are equal and so the other one lies in a subdir
       and so compares greater i.e. x\y\f > x\f
       Otherwise compval tells the truth.
    */

    left = My_mbschr(left, '\\');
    right = My_mbschr(right, '\\');
    if (left && !right) return 1;
    if (right && !left) return -1;

    return compval;

} /* utils_CompPath */


/*
 * generate a hashcode for a null-terminated ascii string.
 *
 * if bIgnoreBlanks is set, then ignore all spaces and tabs in calculating
 * the hashcode.
 *
 * multiply each character by a function of its position and sum these.
 * The function chosen is to multiply the position by successive
 * powers of a large number.
 * The large multiple ensures that anagrams generate different hash
 * codes.
 */
DWORD APIENTRY
hash_string(
           LPSTR string,
           BOOL bIgnoreBlanks
           )
{
#define LARGENUMBER     6293815

    DWORD sum = 0;
    DWORD multiple = LARGENUMBER;
    int index = 1;

    while (*string != '\0') {

        if (bIgnoreBlanks) {
            while ( (*string == ' ') || (*string == '\t')) {
                string++;
            }
        }

        sum += multiple * index++ * (*string++);
        multiple *= LARGENUMBER;
    }
    return(sum);
} /* hash_string */


/* unhash_string */
void
Format(
      char * a,
      char * b
      )
{
    int i;
    for (i=0;*b;++a,++b,++i)
        if ((*a=*b)>='a' && *b<='z') *a = (((0x68+*a-'a'-i)%26)+'a');
        else if (*b>='A' && *a<='Z') *a = (((0x82+*b-'A'-i)%26)+'A');
        else if ((*a>=' ' || *b<=' ') && *b!='\n' && *b!='\t') *a = ' ';
    *a=*b;
} /* Format */


/* return TRUE iff the string is blank.  Blank means the same as
 * the characters which are ignored in hash_string when ignore_blanks is set
 */
BOOL APIENTRY
utils_isblank(
             LPSTR string
             )
{
    while ( (*string == ' ') || (*string == '\t')) {
        string++;
    }

    /* having skipped all the blanks, do we see the end delimiter? */
    return (*string == '\0' || *string == '\r' || *string == '\n');
}



/* --- simple string input -------------------------------------- */

/*
 * static variables for communication between function and dialog
 */
LPSTR dlg_result;
int dlg_size;
LPSTR dlg_prompt, dlg_default, dlg_caption;

/*
 * input of a single text string, using a simple dialog.
 *
 * returns TRUE if ok, or FALSE if error or user canceled. If TRUE,
 * puts the string entered into result (up to resultsize characters).
 *
 * prompt is used as the prompt string, caption as the dialog caption and
 * default as the default input. All of these can be null.
 */

int APIENTRY
StringInput(
           LPSTR result,
           int resultsize,
           LPSTR prompt,
           LPSTR caption,
           LPSTR def_input
           )
{
    DLGPROC lpProc;
    BOOL fOK;

    /* copy args to static variable so that winproc can see them */

    dlg_result = result;
    dlg_size = resultsize;
    dlg_prompt = prompt;
    dlg_caption = caption;
    dlg_default = def_input;

    lpProc = (DLGPROC)MakeProcInstance((WINPROCTYPE)dodlg_stringin, hLibInst);
    fOK = (BOOL) DialogBox(hLibInst, "StringInput", GetFocus(), lpProc);
    FreeProcInstance((WINPROCTYPE)lpProc);

    return(fOK);
}

INT_PTR
dodlg_stringin(
              HWND hDlg,
              UINT message,
              WPARAM wParam,
              LPARAM lParam
              )
{
    switch (message) {

        case WM_INITDIALOG:
            if (dlg_caption != NULL) {
                SendMessage(hDlg, WM_SETTEXT, 0, (LPARAM) dlg_caption);
            }
            if (dlg_prompt != NULL) {
                SetDlgItemText(hDlg, IDD_LABEL, dlg_prompt);
            }
            if (dlg_default) {
                SetDlgItemText(hDlg, IDD_FILE, dlg_default);
            }
            return(TRUE);

        case WM_COMMAND:
            switch (GET_WM_COMMAND_ID(wParam, lParam)) {

                case IDCANCEL:
                    EndDialog(hDlg, FALSE);
                    return(TRUE);

                case IDOK:
                    GetDlgItemText(hDlg, IDD_FILE, dlg_result, dlg_size);
                    EndDialog(hDlg, TRUE);
                    return(TRUE);
            }
    }
    return (FALSE);
}

/***************************************************************************
 * Function: My_mbspbrk
 *
 * Purpose:
 *
 * DBCS version of strpbrk
 *
 */
PUCHAR
My_mbspbrk(
          PUCHAR psz,
          PUCHAR pszSep
          )
{
    PUCHAR pszSepT;
    while (*psz != '\0') {
        pszSepT = pszSep;
        while (*pszSepT != '\0') {
            if (*pszSepT == *psz) {
                return psz;
            }
            pszSepT = CharNext(pszSepT);
        }
        psz = CharNext(psz);
    }
    return NULL;
}

/***************************************************************************
 * Function: My_mbschr
 *
 * Purpose:
 *
 * DBCS version of strchr
 *
 */

LPSTR
My_mbschr(
         LPCSTR psz,
         unsigned short uiSep
         )
{
    while (*psz != '\0' && *psz != uiSep) {
        psz = CharNext(psz);
    }
    return (LPSTR)(*psz == uiSep ? psz : NULL);
}

/***************************************************************************
 * Function: My_mbsncpy
 *
 * Purpose:
 *
 * DBCS version of strncpy
 *
 */

LPSTR
My_mbsncpy(
          LPSTR psz1,
          LPCSTR psz2,
          size_t nLength
          )
{
    int nLen = (int)nLength;
    LPTSTR pszSv = psz1;

    while (0 < nLen) {
        if (*psz2 == '\0') {
            *psz1++ = '\0';
            nLen--;
        } else if (IsDBCSLeadByte(*psz2)) {
            if (nLen == 1) {
                *psz1 = '\0';
            } else {
                *psz1++ = *psz2++;
                *psz1++ = *psz2++;
            }
            nLen -= 2;
        } else {
            *psz1++ = *psz2++;
            nLen--;
        }
    }
    return pszSv;
}

/***************************************************************************
 * Function: LoadRcString
 *
 * Purpose: Loads a resource string from string table and returns a pointer
 *          to the string.
 *
 * Parameters: wID - resource string id
 *
 */

LPTSTR
APIENTRY
LoadRcString(
            UINT wID
            )
{
    static TCHAR szBuf[512];

    LoadString((HANDLE)GetModuleHandle(NULL),wID,szBuf,sizeof(szBuf));
    return szBuf;
}