/*
 * This module serves to demonstrate one way a sophisticated DDE server
 * that uses enumerable topics and items might be implemented.  It takes
 * full advantage of appowned data handles (when fAppowned is set) to
 * minimize the need for repeated rendering of data when shared with
 * multiple clients.
 *
 * The server supports full system topic information plus help and non
 * system topic item enumeration for the benefit of browsing clients
 * that are wondering what's around.
 *
 * This server can be made secure by altering the conversation context
 * filter.
 *
 * This server can appear to support alternate codepages and languages
 * by altering the conversation context filter.   On Windows this is
 * pretty much moot since there is not yet a clearly defined way of
 * doing international communication and because the atom manager restricts
 * what topic and item strings can be used on the system.
 */

#include "ddemlsv.h"
#include "huge.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>

/*
 * This function verifies that the incomming conversation context fits the
 * server's context filter's requirements.
 */
BOOL ValidateContext(
PCONVCONTEXT pCC)
{
    // make sure our CCFilter allows it...mock security, language support
    // old DDE app client case...pCC == NULL
    if (pCC == NULL &&
            CCFilter.dwSecurity == 0 &&      // were nonsecure
            CCFilter.iCodePage == CP_WINANSI // were normal cp
            ) {
        return(TRUE);
    }

    if (pCC &&
            pCC->wFlags == CCFilter.wFlags && // no special flags needed
            pCC->iCodePage == CCFilter.iCodePage && // codepages match
            pCC->dwSecurity == CCFilter.dwSecurity) { // security passes
        // dont care about language and country.
        return(TRUE);
    }
    return(FALSE);  // disallow no match
}


/***************************** Public  Function ****************************\
*
* This function is called by the DDE manager DLL and passes control onto
* the apropriate function pointed to by the global topic and item arrays.
* It handles all DDE interaction generated by external events.
*
\***************************************************************************/
HDDEDATA EXPENTRY __export DdeCallback(
WORD wType,
WORD wFmt,
HCONV hConv,
HSZ hszTopic,
HSZ hszItem,
HDDEDATA hData,
DWORD lData1,
DWORD lData2)
{
    WORD i, j;
    register ITEMLIST *pItemList;
    WORD cItems, iFmt;
    HDDEDATA hDataRet;

    /*
     * Block this callback if its blockable and we are supposed to.
     */
    if (fBlockNextCB && !(wType & XTYPF_NOBLOCK)) {
        fBlockNextCB = FALSE;
        fAllEnabled = FALSE;
        return(CBR_BLOCK);
    }

    /*
     * Block this callback if its associated with a conversation and we
     * are supposed to.
     */
    if (fTermNextCB && hConv) {
        fTermNextCB = FALSE;
        DdeDisconnect(hConv);
        wType = XTYP_DISCONNECT;
    }

    /*
     * Keep a count of connections
     */
    if (wType == XTYP_CONNECT_CONFIRM) {
        cServers++;
        InvalidateRect(hwndServer, &rcConnCount, TRUE);
        return(0);
    }
    if (wType == XTYP_DISCONNECT) {
        cServers--;
        InvalidateRect(hwndServer, &rcConnCount, TRUE);
        return(0);
    }


    /*
     * only allow transactions on the formats we support if they have a format.
     */
    if (wFmt) {
        for (iFmt = 0; iFmt < CFORMATS; iFmt++) {
            if (wFmt == aFormats[iFmt].atom)
                break;
        }
        if (iFmt == CFORMATS)
            return(0);          // illegal format - ignore now.
    }

    /*
     * Executes are allowed only on the system topic.  This is a general
     * convention, not a requirement.
     *
     * Any executes received result in the execute text being shown in
     * the server client area.  No real action is taken.
     */
    if (wType == XTYP_EXECUTE) {
        if (hszTopic == topicList[0].hszTopic) {    // must be on system topic
            // Format is assumed to be CF_TEXT.
            DdeGetData(hData, (LPBYTE)szExec, MAX_EXEC, 0);
            szExec[MAX_EXEC - 1] = '\0';
            InvalidateRect(hwndServer, &rcExec, TRUE);
            hDataRet = TRUE;
            goto ReturnSpot;
        }
        pszComment = "Execute received on non-system topic - ignored";
        InvalidateRect(hwndServer, &rcComment, TRUE);
        return(0);
    }

    /*
     * Process wild initiates here
     */
    if (wType == XTYP_WILDCONNECT) {
        HSZ ahsz[(CTOPICS + 1) * 2];
        /*
         * He wants a hsz list of all our available app/topic pairs
         * that conform to hszTopic and hszItem(App).
         */

        if (!ValidateContext((PCONVCONTEXT)lData1)) {
            return(FALSE);
        }

        if (hszItem != hszAppName && hszItem != NULL) {
            // we only support the hszAppName service
            return(0);
        }

        // scan the topic table and create hsz pairs
        j = 0;
        for (i = 0; i < CTOPICS; i++) {
            if (hszTopic == NULL || hszTopic == topicList[i].hszTopic) {
                ahsz[j++] = hszAppName;
                ahsz[j++] = topicList[i].hszTopic;
            }
        }

        // cap off the list with 0s
        ahsz[j++] = ahsz[j++] = 0L;

        // send it back
        return(DdeCreateDataHandle(idInst, (LPBYTE)&ahsz[0], sizeof(HSZ) * j, 0L, 0, wFmt, 0));
    }

    /*
     * Check our hsz tables and send to the apropriate proc. to process.
     * We use DdeCmpStringHandles() which is the portable case-insensitive
     * method of comparing string handles.  (this is a macro on windows so
     * there is no real speed hit.)  On WINDOWS, HSZs are case-insensitive
     * anyway, but this may not be the case on other platforms.
     */
    for (i = 0; i < CTOPICS; i++) {
        if (DdeCmpStringHandles(topicList[i].hszTopic, hszTopic) == 0) {

            /*
             * connections must be on a topic we support.
             */
            if (wType == XTYP_CONNECT) {
                return(ValidateContext((PCONVCONTEXT)lData1));
            }

            pItemList = topicList[i].pItemList;
            cItems = topicList[i].cItems;
            for (j = 0; j < cItems; j++) {
                if (DdeCmpStringHandles(pItemList[j].hszItem, hszItem) == 0) {

                    /*
                     * Make call to worker function here...
                     */
                    hDataRet = (*pItemList[j].npfnCallback)
                            ((PXFERINFO)&lData2, iFmt);


ReturnSpot:
                    /*
                     * The table functions return a boolean or data.
                     * It gets translated here.
                     */
                    switch (wType & XCLASS_MASK) {
                    case XCLASS_DATA:
                        return(hDataRet);
                        break;
                    case XCLASS_FLAGS:
                        return(hDataRet ? DDE_FACK : DDE_FNOTPROCESSED);
                        break;
                    case XCLASS_BOOL:
                        return(TRUE);
                    default: // XCLASS_NOTIFICATION
                        return(0);
                        break;
                    }
                    break;
                }
            }
            break;
        }
    }

    /*
     * anything else fails - DDEML is designed so that a 0 return is ALWAYS ok.
     */
    return(0);
}





/***************************** Private Function ****************************\
* This passes out a standard tab-delimited list of topic names for this
* application.
*
* This support is required for other apps to be able to
* find out about us.  This kind of support should be in every DDE
* application.
*
\***************************************************************************/
HDDEDATA TopicListXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
    WORD cbAlloc, i;
    LPSTR pszTopicList;
    HDDEDATA hData;

    if (pXferInfo->wType == XTYP_ADVSTART)
        return(TRUE);

    if (pXferInfo->wType != XTYP_REQUEST &&
            pXferInfo->wType != XTYP_ADVREQ)
        return(0);
    /*
     * construct the list of topics we have
     */
    cbAlloc = 0;
    for (i = 0; i < CTOPICS; i++)
        cbAlloc += lstrlen(topicList[i].pszTopic) + 1;  // 1 for tab

    // allocate a data handle big enough for the list.
    hData = DdeCreateDataHandle(idInst, NULL, 0, cbAlloc, pXferInfo->hszItem,
            pXferInfo->wFmt, 0);
    pszTopicList = (LPSTR)DdeAccessData(hData, NULL);
    if (pszTopicList) {
        for (i = 0; i < CTOPICS; i++) {
            _fstrcpy(pszTopicList, topicList[i].pszTopic);
            pszTopicList += strlen(topicList[i].pszTopic);
            *pszTopicList++ = '\t';
        }
        *--pszTopicList = '\0';
        DdeUnaccessData(hData);
        return(hData);
    }
    return(0);
}




/***************************** Private Function ****************************\
* This passes out a standard tab-delimited list of item names for the
* specified topic.
*
* This support is required for other apps to be able to
* find out about us.  This kind of support should be in every DDE
* application.
*
\***************************************************************************/
HDDEDATA ItemListXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
    WORD cbAlloc, i, iItem, cItems;
    ITEMLIST *pItemList = 0;
    LPSTR pszItemList;
    HDDEDATA hData;

    if (pXferInfo->wType == XTYP_ADVSTART)
        return(TRUE);

    if (pXferInfo->wType != XTYP_REQUEST &&
                pXferInfo->wType != XTYP_ADVREQ)
        return(0);
    /*
     * construct the list of items we support for this topic - this supports
     * more than the minimum standard which would support SysItems only on
     * the system topic.
     */

    // locate the requested topic item table
    for (i = 0; i < CTOPICS; i++) {
        if (pXferInfo->hszTopic == topicList[i].hszTopic) {
            pItemList = topicList[i].pItemList;
            cItems = topicList[i].cItems;
            break;
        }
    }

    if (!pItemList)
        return(0);  // item not found

    cbAlloc = 0;
    for (iItem = 0; iItem < cItems; iItem++)
        cbAlloc += lstrlen(pItemList[iItem].pszItem) + 1; // 1 for tab

    // allocate a data handle big enough for the list.
    hData = DdeCreateDataHandle(idInst, NULL, 0, cbAlloc, pXferInfo->hszItem,
            pXferInfo->wFmt, 0);
    pszItemList = (LPSTR)DdeAccessData(hData, NULL);
    if (pszItemList) {
        for (i = 0; i < cItems; i++) {
            _fstrcpy(pszItemList, pItemList[i].pszItem);
            pszItemList += strlen(pItemList[i].pszItem);
            *pszItemList++ = '\t';
        }
        *--pszItemList = '\0';
        DdeUnaccessData(hData);
        return(hData);
    }
    return(0);
}





/***************************** Private Function ****************************\
* Gives out a 0 terminated array of dde format numbers supported by this app.
*
* This support is required for other apps to be able to
* find out about us.  This kind of support should be in every DDE
* application.
*
\***************************************************************************/
HDDEDATA sysFormatsXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
    int i, cb;
    LPSTR psz, pszT;
    HDDEDATA hData;

    if (pXferInfo->wType == XTYP_ADVSTART)
        return(TRUE);

    if (pXferInfo->wType != XTYP_REQUEST &&
            pXferInfo->wType != XTYP_ADVREQ)
        return(0);

    for (i = 0, cb = 0; i < CFORMATS; i++)
        cb += strlen(aFormats[i].sz) + 1;

    hData = DdeCreateDataHandle(idInst, NULL, (DWORD)cb,
            0L, pXferInfo->hszItem, pXferInfo->wFmt, 0);
    psz = pszT = DdeAccessData(hData, NULL);
    for (i = 0; i < CFORMATS; i++) {
        _fstrcpy(pszT, aFormats[i].sz);
        pszT += _fstrlen(pszT);
        *pszT++ = '\t';
    }
    *(--pszT) = '\0';
    DdeUnaccessData(hData);
    return(hData);
}



/*
 * This is a runaway item.  Each time it is requested, it changes.
 * pokes just make it change again.
 */
HDDEDATA TestRandomXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
    char szT[10];   // SS==DS!
    LPSTR pszData;
    HDDEDATA hData;
    WORD i;

    switch (pXferInfo->wType) {
    case XTYP_POKE:
        // we expect an ascii number to replace the current seed.
        pszComment = "Rand poke received.";
        InvalidateRect(hwndServer, &rcComment, TRUE);
        InvalidateRect(hwndServer, &rcRand, TRUE);
        if (DdeGetData(pXferInfo->hData, szT, 10, 0)) {
            szT[9] = '\0';  // just incase we overran.
            sscanf(szT, "%d", &seed);
            for (i = 0; i < CFORMATS; i++) {
                if (hDataRand[i])
                    DdeFreeDataHandle(hDataRand[i]);
                hDataRand[i] = 0;
            }
            DdePostAdvise(idInst, pXferInfo->hszTopic, pXferInfo->hszItem);
            return(1);
        }
        break;

    case XTYP_REQUEST:
        pszComment = "Rand data requested.";
        InvalidateRect(hwndServer, &rcComment, TRUE);
    case XTYP_ADVREQ:
        Delay(RenderDelay, FALSE);
        if (!hDataRand[iFmt]) {
            hDataRand[iFmt] = DdeCreateDataHandle(idInst, NULL, 0, 10, pXferInfo->hszItem,
                pXferInfo->wFmt, fAppowned ? HDATA_APPOWNED : 0);
            if (pszData = DdeAccessData(hDataRand[iFmt], NULL)) {
                wsprintf(pszData, "%d", seed);
                DdeUnaccessData(hDataRand[iFmt]);
            }
        }
        hData = hDataRand[iFmt];
        if (!fAppowned)
            hDataRand[iFmt] = 0;
        return(hData);
        break;

    case XTYP_ADVSTART:
        return(1);
    }
    return(0);
}

/*
 * This is a runaway item.  Each time it is requested, it changes.
 * pokes just make it change again.
 */
HDDEDATA TestCountXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
    char szT[16];   // SS==DS!
    LPSTR pszData;
    HDDEDATA hData;
    WORD i;

    switch (pXferInfo->wType) {
    case XTYP_POKE:
        // we expect an ascii number to replace the current count.
        pszComment = "Count poke received";
        InvalidateRect(hwndServer, &rcComment, TRUE);
        InvalidateRect(hwndServer, &rcCount, TRUE);
        if (DdeGetData(pXferInfo->hData, szT, 10, 0)) {
            szT[9] = '\0';  // just incase we overran.
            sscanf(szT, "%ld", &count);
            for (i = 0; i < CFORMATS; i++) {
                if (hDataCount[i])
                    DdeFreeDataHandle(hDataCount[i]);
                hDataCount[i] = 0;
            }
            DdePostAdvise(idInst, pXferInfo->hszTopic, pXferInfo->hszItem);
            return(1);
        }
        break;

    case XTYP_REQUEST:
        pszComment = "Count data requested.";
        InvalidateRect(hwndServer, &rcComment, TRUE);
    case XTYP_ADVREQ:
        Delay(RenderDelay, FALSE);
        if (!hDataCount[iFmt]) {
            hDataCount[iFmt] = DdeCreateDataHandle(idInst, NULL, 0, 10, pXferInfo->hszItem,
                    pXferInfo->wFmt, fAppowned ? HDATA_APPOWNED : 0);
            if (pszData = DdeAccessData(hDataCount[iFmt], NULL)) {
                wsprintf(pszData, "%ld", count);
                DdeUnaccessData(hDataCount[iFmt]);
            }
        }
        hData = hDataCount[iFmt];
        if (!fAppowned)
            hDataCount[iFmt] = 0;
        return(hData);
        break;

    case XTYP_ADVSTART:
        return(1);
    }
    return(0);
}


/*
 * This is not a runaway item.  Only Pokes make it change.
 */
HDDEDATA TestHugeXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
    BOOL fSuccess;
    DWORD ulcb;
    LPBYTE lpData;
    WORD i;
    HDDEDATA hData;

    switch (pXferInfo->wType) {
    case XTYP_POKE:
        ulcb = DdeGetData(pXferInfo->hData, NULL, 0, 0);
        fSuccess = CheckHugeData(pXferInfo->hData);
        if (fSuccess) {
            pszComment = "Huge poke data successfully received.";
        } else {
            wsprintf(szComment, "%ld bytes of invalid Huge data received.", ulcb);
            pszComment = szComment;
        }
        InvalidateRect(hwndServer, &rcComment, TRUE);
        InvalidateRect(hwndServer, &rcHugeSize, TRUE);
        if (fSuccess) {
            for (i = 0; i < CFORMATS; i++) {
                if (hDataHuge[i]) {
                    DdeFreeDataHandle(hDataHuge[i]);
                    hDataHuge[i] = 0;
                }
            }
            /*
             * Since callback data handles are only good for the duration of
             * the callback, we must copy the data to our own data handle.
             */
            lpData = DdeAccessData(pXferInfo->hData, &cbHuge);
            hDataHuge[iFmt] = DdeCreateDataHandle(idInst, lpData, cbHuge, 0,
                    pXferInfo->hszItem, pXferInfo->wFmt, fAppowned ? HDATA_APPOWNED : 0);
            DdeUnaccessData(pXferInfo->hData);
            DdePostAdvise(idInst, pXferInfo->hszTopic, pXferInfo->hszItem);
        }
        return(fSuccess);
        break;

    case XTYP_REQUEST:
        pszComment = "Huge data requested.";
        InvalidateRect(hwndServer, &rcComment, TRUE);
    case XTYP_ADVREQ:
        Delay(RenderDelay, FALSE);
        if (!hDataHuge[iFmt]) {
            cbHuge = (DWORD)rand() * 64L + 0x10000L;
            wsprintf(szComment, "Generating huge data - length=%ld...", cbHuge);
            pszComment = szComment;
            InvalidateRect(hwndServer, &rcComment, TRUE);
            UpdateWindow(hwndServer);
            hDataHuge[iFmt] = CreateHugeDataHandle(cbHuge, 4325, 345, 5,
                    pXferInfo->hszItem,
                    pXferInfo->wFmt, fAppowned ? HDATA_APPOWNED : 0);
            pszComment = "";
            InvalidateRect(hwndServer, &rcComment, TRUE);
            InvalidateRect(hwndServer, &rcHugeSize, TRUE);
        }
        hData = hDataHuge[iFmt];
        if (!fAppowned)
            hDataHuge[iFmt] = 0;
        return(hData);
        break;

    case XTYP_ADVSTART:
        return(1);
    }
    return(0);
}


HDDEDATA HelpXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
    HDDEDATA hData;

    switch (pXferInfo->wType) {
    case XTYP_REQUEST:
        pszComment = "Help text requested.";
        InvalidateRect(hwndServer, &rcComment, TRUE);
    case XTYP_ADVREQ:
        if (!hDataHelp[iFmt]) {
            hDataHelp[iFmt] = DdeCreateDataHandle(idInst, szDdeHelp, strlen(szDdeHelp) + 1,
                    0, pXferInfo->hszItem, pXferInfo->wFmt, fAppowned ? HDATA_APPOWNED : 0);
        }
        hData = hDataHelp[iFmt];
        if (!fAppowned)
            hDataHelp[iFmt] = 0;
        return(hData);
        break;

    case XTYP_ADVSTART:
        return(1);
    }
    return(0);
}


/***************************** Private Function ****************************\
*  This creates often used global hszs from standard global strings.
*  It also fills the hsz fields of the topic and item tables.
*
\***************************************************************************/
void Hszize()
{
    register ITEMLIST *pItemList;
    WORD iTopic, iItem;

    hszAppName = DdeCreateStringHandle(idInst, szServer, NULL);

    for (iTopic = 0; iTopic < CTOPICS; iTopic++) {
        topicList[iTopic].hszTopic =
                DdeCreateStringHandle(idInst, topicList[iTopic].pszTopic, NULL);
        pItemList = topicList[iTopic].pItemList;
        for (iItem = 0; iItem < topicList[iTopic].cItems; iItem++) {
            pItemList[iItem].hszItem =
                    DdeCreateStringHandle(idInst, pItemList[iItem].pszItem, NULL);
        }
    }
}





/***************************** Private Function ****************************\
*  This destroys often used global hszs from standard global strings.
*
\***************************************************************************/
void UnHszize()
{
    register ITEMLIST *pItemList;
    WORD iTopic, iItem;

    DdeFreeStringHandle(idInst, hszAppName);

    for (iTopic = 0; iTopic < CTOPICS; iTopic++) {
        DdeFreeStringHandle(idInst, topicList[iTopic].hszTopic);
        pItemList = topicList[iTopic].pItemList;
        for (iItem = 0; iItem < topicList[iTopic].cItems; iItem++) {
            DdeFreeStringHandle(idInst, pItemList[iItem].hszItem);
        }
    }
}
