/////////////////////////////////////////////////////////////////////////////
// Name:        src/msw/dde.cpp
// Purpose:     DDE classes
// Author:      Julian Smart
// Modified by:
// Created:     01/02/97
// Copyright:   (c) Julian Smart
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#if wxUSE_IPC

#ifndef WX_PRECOMP
    #include "wx/utils.h"
    #include "wx/app.h"
    #include "wx/hashmap.h"
    #include "wx/module.h"
#endif

#include "wx/dde.h"
#include "wx/intl.h"
#include "wx/buffer.h"
#include "wx/strconv.h"

#include "wx/msw/private.h"

#include <string.h>
#include <ddeml.h>

// ----------------------------------------------------------------------------
// macros and constants
// ----------------------------------------------------------------------------

#ifdef __WIN32__
    #define _EXPORT
#else
    #define _EXPORT _export
#endif

#if wxUSE_UNICODE
    #define DDE_CP      CP_WINUNICODE
#else
    #define DDE_CP      CP_WINANSI
#endif

#define GetHConv()       ((HCONV)m_hConv)

// default timeout for DDE operations (5sec)
#define DDE_TIMEOUT     5000

// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------

static wxDDEConnection *DDEFindConnection(HCONV hConv);
static void DDEDeleteConnection(HCONV hConv);
static wxDDEServer *DDEFindServer(const wxString& s);

extern "C" HDDEDATA EXPENTRY _EXPORT _DDECallback(WORD wType,
                                                  WORD wFmt,
                                                  HCONV hConv,
                                                  HSZ hsz1,
                                                  HSZ hsz2,
                                                  HDDEDATA hData,
                                                  DWORD lData1,
                                                  DWORD lData2);

// Add topic name to atom table before using in conversations
static HSZ DDEAddAtom(const wxString& string);
static HSZ DDEGetAtom(const wxString& string);

// string handles
static HSZ DDEAtomFromString(const wxString& s);
static wxString DDEStringFromAtom(HSZ hsz);
static void DDEFreeString(HSZ hsz);

// error handling
static wxString DDEGetErrorMsg(UINT error);
static void DDELogError(const wxString& s, UINT error = DMLERR_NO_ERROR);

// ----------------------------------------------------------------------------
// global variables
// ----------------------------------------------------------------------------

WX_DECLARE_STRING_HASH_MAP( HSZ, wxAtomMap );

static DWORD DDEIdInst = 0L;
static wxDDEConnection *DDECurrentlyConnecting = NULL;
static wxAtomMap wxAtomTable;

#include "wx/listimpl.cpp"

WX_DEFINE_LIST(wxDDEClientList)
WX_DEFINE_LIST(wxDDEServerList)
WX_DEFINE_LIST(wxDDEConnectionList)

static wxDDEClientList wxDDEClientObjects;
static wxDDEServerList wxDDEServerObjects;

static bool DDEInitialized = false;

// ----------------------------------------------------------------------------
// private classes
// ----------------------------------------------------------------------------

// A module to allow DDE cleanup without calling these functions
// from app.cpp or from the user's application.

class wxDDEModule : public wxModule
{
public:
    wxDDEModule() {}
    bool OnInit() { return true; }
    void OnExit() { wxDDECleanUp(); }

private:
    DECLARE_DYNAMIC_CLASS(wxDDEModule)
};

// ----------------------------------------------------------------------------
// wxWin macros
// ----------------------------------------------------------------------------

IMPLEMENT_DYNAMIC_CLASS(wxDDEServer, wxServerBase)
IMPLEMENT_DYNAMIC_CLASS(wxDDEClient, wxClientBase)
IMPLEMENT_DYNAMIC_CLASS(wxDDEConnection, wxConnectionBase)
IMPLEMENT_DYNAMIC_CLASS(wxDDEModule, wxModule)

// ============================================================================
// implementation
// ============================================================================

// ----------------------------------------------------------------------------
// initialization and cleanup
// ----------------------------------------------------------------------------

extern void wxDDEInitialize()
{
    if ( !DDEInitialized )
    {
        // Should insert filter flags
        PFNCALLBACK callback = (PFNCALLBACK)
            MakeProcInstance((FARPROC)_DDECallback, wxGetInstance());
        UINT rc = DdeInitialize(&DDEIdInst, callback, APPCLASS_STANDARD, 0L);
        if ( rc != DMLERR_NO_ERROR )
        {
            DDELogError(wxT("Failed to initialize DDE"), rc);
        }
        else
        {
            DDEInitialized = true;
        }
    }
}

void wxDDECleanUp()
{
    // deleting them later won't work as DDE won't be initialized any more
    wxASSERT_MSG( wxDDEServerObjects.empty() &&
                    wxDDEClientObjects.empty(),
                    wxT("all DDE objects should be deleted by now") );

    wxAtomTable.clear();

    if ( DDEIdInst != 0 )
    {
        DdeUninitialize(DDEIdInst);
        DDEIdInst = 0;
    }
}

// ----------------------------------------------------------------------------
// functions working with the global connection list(s)
// ----------------------------------------------------------------------------

// Global find connection
static wxDDEConnection *DDEFindConnection(HCONV hConv)
{
    wxDDEServerList::compatibility_iterator serverNode = wxDDEServerObjects.GetFirst();
    wxDDEConnection *found = NULL;
    while (serverNode && !found)
    {
        wxDDEServer *object = serverNode->GetData();
        found = object->FindConnection((WXHCONV) hConv);
        serverNode = serverNode->GetNext();
    }

    if (found)
    {
        return found;
    }

    wxDDEClientList::compatibility_iterator clientNode = wxDDEClientObjects.GetFirst();
    while (clientNode && !found)
    {
        wxDDEClient *object = clientNode->GetData();
        found = object->FindConnection((WXHCONV) hConv);
        clientNode = clientNode->GetNext();
    }
    return found;
}

// Global delete connection
static void DDEDeleteConnection(HCONV hConv)
{
    wxDDEServerList::compatibility_iterator serverNode = wxDDEServerObjects.GetFirst();
    bool found = false;
    while (serverNode && !found)
    {
        wxDDEServer *object = serverNode->GetData();
        found = object->DeleteConnection((WXHCONV) hConv);
        serverNode = serverNode->GetNext();
    }
    if (found)
    {
        return;
    }

    wxDDEClientList::compatibility_iterator clientNode = wxDDEClientObjects.GetFirst();
    while (clientNode && !found)
    {
        wxDDEClient *object = clientNode->GetData();
        found = object->DeleteConnection((WXHCONV) hConv);
        clientNode = clientNode->GetNext();
    }
}

// Find a server from a service name
static wxDDEServer *DDEFindServer(const wxString& s)
{
    wxDDEServerList::compatibility_iterator node = wxDDEServerObjects.GetFirst();
    wxDDEServer *found = NULL;
    while (node && !found)
    {
        wxDDEServer *object = node->GetData();

        if (object->GetServiceName() == s)
        {
            found = object;
        }
        else
        {
            node = node->GetNext();
        }
    }

    return found;
}

// ----------------------------------------------------------------------------
// wxDDEServer
// ----------------------------------------------------------------------------

wxDDEServer::wxDDEServer()
{
    wxDDEInitialize();

    wxDDEServerObjects.Append(this);
}

bool wxDDEServer::Create(const wxString& server)
{
    m_serviceName = server;

    HSZ hsz = DDEAtomFromString(server);

    if ( !hsz )
    {
        return false;
    }


    bool success = (DdeNameService(DDEIdInst, hsz, (HSZ) NULL, DNS_REGISTER)
        != NULL);

    if (!success)
    {
        DDELogError(wxString::Format(_("Failed to register DDE server '%s'"),
            server.c_str()));
    }

    DDEFreeString(hsz);

    return success;
}

wxDDEServer::~wxDDEServer()
{
    if ( !m_serviceName.empty() )
    {
        HSZ hsz = DDEAtomFromString(m_serviceName);

        if (hsz)
        {
            if ( !DdeNameService(DDEIdInst, hsz,
                (HSZ) NULL, DNS_UNREGISTER) )
            {
                DDELogError(wxString::Format(
                    _("Failed to unregister DDE server '%s'"),
                    m_serviceName.c_str()));
            }

            DDEFreeString(hsz);
        }
    }

    wxDDEServerObjects.DeleteObject(this);

    wxDDEConnectionList::compatibility_iterator node = m_connections.GetFirst();
    while (node)
    {
        wxDDEConnection *connection = node->GetData();
        wxDDEConnectionList::compatibility_iterator next = node->GetNext();
        connection->OnDisconnect(); // May delete the node implicitly
        node = next;
    }

    // If any left after this, delete them
    node = m_connections.GetFirst();
    while (node)
    {
        wxDDEConnection *connection = node->GetData();
        wxDDEConnectionList::compatibility_iterator next = node->GetNext();
        delete connection;
        node = next;
    }
}

wxConnectionBase *wxDDEServer::OnAcceptConnection(const wxString& /* topic */)
{
    return new wxDDEConnection;
}

wxDDEConnection *wxDDEServer::FindConnection(WXHCONV conv)
{
    wxDDEConnectionList::compatibility_iterator node = m_connections.GetFirst();
    wxDDEConnection *found = NULL;
    while (node && !found)
    {
        wxDDEConnection *connection = node->GetData();
        if (connection->m_hConv == conv)
            found = connection;
        else node = node->GetNext();
    }
    return found;
}

// Only delete the entry in the map, not the actual connection
bool wxDDEServer::DeleteConnection(WXHCONV conv)
{
    wxDDEConnectionList::compatibility_iterator node = m_connections.GetFirst();
    while (node)
    {
        wxDDEConnection *connection = node->GetData();
        if (connection->m_hConv == conv)
        {
            m_connections.Erase(node);
            return true;
        }
        else
        {
            node = node->GetNext();
        }
    }
    return false;
}

// ----------------------------------------------------------------------------
// wxDDEClient
// ----------------------------------------------------------------------------

wxDDEClient::wxDDEClient()
{
    wxDDEInitialize();

    wxDDEClientObjects.Append(this);
}

wxDDEClient::~wxDDEClient()
{
    wxDDEClientObjects.DeleteObject(this);
    wxDDEConnectionList::compatibility_iterator node = m_connections.GetFirst();
    while (node)
    {
        wxDDEConnection *connection = node->GetData();
        delete connection;  // Deletes the node implicitly (see ~wxDDEConnection)
        node = m_connections.GetFirst();
    }
}

bool wxDDEClient::ValidHost(const wxString& /* host */)
{
    return true;
}

wxConnectionBase *wxDDEClient::MakeConnection(const wxString& WXUNUSED(host),
                                              const wxString& server,
                                              const wxString& topic)
{
    HSZ hszServer = DDEAtomFromString(server);

    if ( !hszServer )
    {
        return NULL;
    }


    HSZ hszTopic = DDEAtomFromString(topic);

    if ( !hszTopic )
    {
        DDEFreeString(hszServer);
        return NULL;
    }


    HCONV hConv = ::DdeConnect(DDEIdInst, hszServer, hszTopic,
        (PCONVCONTEXT) NULL);

    DDEFreeString(hszServer);
    DDEFreeString(hszTopic);


    if ( !hConv )
    {
        DDELogError( wxString::Format(
            _("Failed to create connection to server '%s' on topic '%s'"),
            server.c_str(), topic.c_str()) );
    }
    else
    {
        wxDDEConnection *connection = (wxDDEConnection*) OnMakeConnection();
        if (connection)
        {
            connection->m_hConv = (WXHCONV) hConv;
            connection->m_topicName = topic;
            connection->m_client = this;
            m_connections.Append(connection);
            return connection;
        }
    }

    return NULL;
}

wxConnectionBase *wxDDEClient::OnMakeConnection()
{
    return new wxDDEConnection;
}

wxDDEConnection *wxDDEClient::FindConnection(WXHCONV conv)
{
    wxDDEConnectionList::compatibility_iterator node = m_connections.GetFirst();
    wxDDEConnection *found = NULL;
    while (node && !found)
    {
        wxDDEConnection *connection = node->GetData();
        if (connection->m_hConv == conv)
            found = connection;
        else node = node->GetNext();
    }
    return found;
}

// Only delete the entry in the map, not the actual connection
bool wxDDEClient::DeleteConnection(WXHCONV conv)
{
    wxDDEConnectionList::compatibility_iterator node = m_connections.GetFirst();
    while (node)
    {
        wxDDEConnection *connection = node->GetData();
        if (connection->m_hConv == conv)
        {
            m_connections.Erase(node);
            return true;
        }
        else node = node->GetNext();
    }
    return false;
}

// ----------------------------------------------------------------------------
// wxDDEConnection
// ----------------------------------------------------------------------------

wxDDEConnection::wxDDEConnection(void *buffer, size_t size)
     : wxConnectionBase(buffer, size)
{
    m_client = NULL;
    m_server = NULL;

    m_hConv = 0;
    m_sendingData = NULL;
}

wxDDEConnection::wxDDEConnection()
     : wxConnectionBase()
{
    m_hConv = 0;
    m_sendingData = NULL;
    m_server = NULL;
    m_client = NULL;
}

wxDDEConnection::~wxDDEConnection()
{
    Disconnect();
    if (m_server)
        m_server->GetConnections().DeleteObject(this);
    else
        m_client->GetConnections().DeleteObject(this);
}

// Calls that CLIENT can make
bool wxDDEConnection::Disconnect()
{
    if ( !GetConnected() )
        return true;

    DDEDeleteConnection(GetHConv());

    bool ok = DdeDisconnect(GetHConv()) != 0;
    if ( !ok )
    {
        DDELogError(wxT("Failed to disconnect from DDE server gracefully"));
    }

    SetConnected( false );  // so we don't try and disconnect again

    return ok;
}

bool
wxDDEConnection::DoExecute(const void *data, size_t size, wxIPCFormat format)
{
    wxCHECK_MSG( format == wxIPC_TEXT ||
                 format == wxIPC_UTF8TEXT ||
                 format == wxIPC_UNICODETEXT,
                 false,
                 wxT("wxDDEServer::Execute() supports only text data") );

    wxMemoryBuffer buffer;
    LPBYTE realData = NULL;
    size_t realSize = 0;
    wxMBConv *conv = NULL;

    // Windows only supports either ANSI or UTF-16 format depending on the
    // build, so we need to convert the data if it doesn't use it already
#if wxUSE_UNICODE
    if ( format == wxIPC_TEXT )
    {
        conv = &wxConvLibc;
    }
    else if ( format == wxIPC_UTF8TEXT )
    {
        conv = &wxConvUTF8;
    }
    else // no conversion necessary for wxIPC_UNICODETEXT
    {
        realData = (LPBYTE)data;
        realSize = size;
    }

    if ( conv )
    {
        const char * const text = (const char *)data;
        const size_t len = size;

        realSize = conv->ToWChar(NULL, 0, text, len);
        if ( realSize == wxCONV_FAILED )
            return false;

        realData = (LPBYTE)buffer.GetWriteBuf(realSize*sizeof(wchar_t));
        if ( !realData )
            return false;

        realSize = conv->ToWChar((wchar_t *)realData, realSize, text, len);
        if ( realSize == wxCONV_FAILED )
            return false;

        // We need to pass the size of the buffer to DdeClientTransaction() and
        // not the length of the string.
        realSize *= sizeof(wchar_t);
    }
#else // !wxUSE_UNICODE
    if ( format == wxIPC_UNICODETEXT )
    {
        conv = &wxConvLibc;
    }
    else if ( format == wxIPC_UTF8TEXT )
    {
        // we could implement this in theory but it's not obvious how to pass
        // the format information and, basically, why bother -- just use
        // Unicode build
        wxFAIL_MSG( wxT("UTF-8 text not supported in ANSI build") );

        return false;
    }
    else // don't convert wxIPC_TEXT
    {
        realData = (LPBYTE)data;
        realSize = size;
    }

    if ( conv )
    {
        const wchar_t * const wtext = (const wchar_t *)data;
        const size_t len = size/sizeof(wchar_t);

        realSize = conv->FromWChar(NULL, 0, wtext, len);
        if ( realSize == wxCONV_FAILED )
            return false;

        realData = (LPBYTE)buffer.GetWriteBuf(realSize);
        if ( !realData )
            return false;

        realSize = conv->FromWChar((char*)realData, realSize, wtext, len);
        if ( realSize == wxCONV_FAILED )
            return false;
    }
#endif // wxUSE_UNICODE/!wxUSE_UNICODE

    DWORD result;
    bool ok = DdeClientTransaction(realData,
                                   realSize,
                                   GetHConv(),
                                   NULL,
                                   // MSDN: if the transaction specified by
                                   // the wType parameter does not pass data
                                   // or is XTYP_EXECUTE, wFmt should be zero.
                                   0,
                                   XTYP_EXECUTE,
                                   DDE_TIMEOUT,
                                   &result) != 0;

    if ( !ok )
    {
        DDELogError(wxT("DDE execute request failed"));
    }

    return ok;
}

const void *wxDDEConnection::Request(const wxString& item, size_t *size, wxIPCFormat format)
{
    DWORD result;

    HSZ atom = DDEGetAtom(item);

    HDDEDATA returned_data = DdeClientTransaction(NULL, 0,
                                                  GetHConv(),
                                                  atom, format,
                                                  XTYP_REQUEST,
                                                  DDE_TIMEOUT,
                                                  &result);
    if ( !returned_data )
    {
        DDELogError(wxT("DDE data request failed"));

        return NULL;
    }

    DWORD len = DdeGetData(returned_data, NULL, 0, 0);

    void *data = GetBufferAtLeast(len);
    wxASSERT_MSG(data != NULL,
                 wxT("Buffer too small in wxDDEConnection::Request") );
    (void) DdeGetData(returned_data, (LPBYTE)data, len, 0);

    (void) DdeFreeDataHandle(returned_data);

    if (size)
        *size = (size_t)len;

    return data;
}

bool wxDDEConnection::DoPoke(const wxString& item, const void *data, size_t size, wxIPCFormat format)
{
    DWORD result;

    HSZ item_atom = DDEGetAtom(item);
    bool ok = DdeClientTransaction((LPBYTE)data,
                                   size,
                                   GetHConv(),
                                   item_atom, format,
                                   XTYP_POKE,
                                   DDE_TIMEOUT,
                                   &result) != 0;
    if ( !ok )
    {
        DDELogError(_("DDE poke request failed"));
    }

    return ok;
}

bool wxDDEConnection::StartAdvise(const wxString& item)
{
    DWORD result;
    HSZ atom = DDEGetAtom(item);

    bool ok = DdeClientTransaction(NULL, 0,
                                   GetHConv(),
                                   atom, CF_TEXT,
                                   XTYP_ADVSTART,
                                   DDE_TIMEOUT,
                                   &result) != 0;
    if ( !ok )
    {
        DDELogError(_("Failed to establish an advise loop with DDE server"));
    }

    return ok;
}

bool wxDDEConnection::StopAdvise(const wxString& item)
{
    DWORD result;
    HSZ atom = DDEGetAtom(item);

    bool ok = DdeClientTransaction(NULL, 0,
                                   GetHConv(),
                                   atom, CF_TEXT,
                                   XTYP_ADVSTOP,
                                   DDE_TIMEOUT,
                                   &result) != 0;
    if ( !ok )
    {
        DDELogError(_("Failed to terminate the advise loop with DDE server"));
    }

    return ok;
}

// Calls that SERVER can make
bool wxDDEConnection::DoAdvise(const wxString& item,
                               const void *data,
                               size_t size,
                               wxIPCFormat format)
{
    HSZ item_atom = DDEGetAtom(item);
    HSZ topic_atom = DDEGetAtom(m_topicName);
    m_sendingData = data;  // mrf: potential for scope problems here?
    m_dataSize = size;
    // wxIPC_PRIVATE does not succeed, so use text instead
    m_dataType = format == wxIPC_PRIVATE ? wxIPC_TEXT : format;

    bool ok = DdePostAdvise(DDEIdInst, topic_atom, item_atom) != 0;
    if ( !ok )
    {
        DDELogError(_("Failed to send DDE advise notification"));
    }

    return ok;
}

// ----------------------------------------------------------------------------
// _DDECallback
// ----------------------------------------------------------------------------

#define DDERETURN HDDEDATA

HDDEDATA EXPENTRY _EXPORT
_DDECallback(WORD wType,
             WORD wFmt,
             HCONV hConv,
             HSZ hsz1,
             HSZ hsz2,
             HDDEDATA hData,
             DWORD WXUNUSED(lData1),
             DWORD WXUNUSED(lData2))
{
    switch (wType)
    {
        case XTYP_CONNECT:
            {
                wxString topic = DDEStringFromAtom(hsz1),
                         srv = DDEStringFromAtom(hsz2);
                wxDDEServer *server = DDEFindServer(srv);
                if (server)
                {
                    wxDDEConnection *connection =
                        (wxDDEConnection*) server->OnAcceptConnection(topic);
                    if (connection)
                    {
                        connection->m_server = server;
                        server->GetConnections().Append(connection);
                        connection->m_hConv = 0;
                        connection->m_topicName = topic;
                        DDECurrentlyConnecting = connection;
                        return (DDERETURN)(DWORD)true;
                    }
                }
                break;
            }

        case XTYP_CONNECT_CONFIRM:
            {
                if (DDECurrentlyConnecting)
                {
                    DDECurrentlyConnecting->m_hConv = (WXHCONV) hConv;
                    DDECurrentlyConnecting = NULL;
                    return (DDERETURN)(DWORD)true;
                }
                break;
            }

        case XTYP_DISCONNECT:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);
                if (connection)
                {
                    connection->SetConnected( false );
                    if (connection->OnDisconnect())
                    {
                        DDEDeleteConnection(hConv);  // Delete mapping: hConv => connection
                        return (DDERETURN)(DWORD)true;
                    }
                }
                break;
            }

        case XTYP_EXECUTE:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection)
                {
                    DWORD len = DdeGetData(hData, NULL, 0, 0);

                    void *data = connection->GetBufferAtLeast(len);
                    wxASSERT_MSG(data != NULL,
                                 wxT("Buffer too small in _DDECallback (XTYP_EXECUTE)") );

                    DdeGetData(hData, (LPBYTE)data, len, 0);

                    DdeFreeDataHandle(hData);

                    // XTYP_EXECUTE can be used for text only and the text is
                    // always in ANSI format for ANSI build and Unicode format
                    // in Unicode build
                    #if wxUSE_UNICODE
                        wFmt = wxIPC_UNICODETEXT;
                    #else
                        wFmt = wxIPC_TEXT;
                    #endif

                    if ( connection->OnExecute(connection->m_topicName,
                                               data,
                                               (int)len,
                                               (wxIPCFormat)wFmt) )
                    {
                        return (DDERETURN)(DWORD)DDE_FACK;
                    }
                }

                return (DDERETURN)DDE_FNOTPROCESSED;
            }

        case XTYP_REQUEST:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection)
                {
                    wxString item_name = DDEStringFromAtom(hsz2);

                    size_t user_size = wxNO_LEN;
                    const void *data = connection->OnRequest(connection->m_topicName,
                                                             item_name,
                                                             &user_size,
                                                             (wxIPCFormat)wFmt);
                    if (data)
                    {
                      if (user_size == wxNO_LEN)
                        switch (wFmt)
                        {
                          case wxIPC_TEXT:
                          case wxIPC_UTF8TEXT:
                            user_size = strlen((const char*)data) + 1;  // includes final NUL
                            break;
                          case wxIPC_UNICODETEXT:
                            user_size = (wcslen((const wchar_t*)data) + 1) * sizeof(wchar_t);  // includes final NUL
                            break;
                          default:
                            user_size = 0;
                        }

                        HDDEDATA handle = DdeCreateDataHandle(DDEIdInst,
                                                              (LPBYTE)data,
                                                              user_size,
                                                              0,
                                                              hsz2,
                                                              wFmt,
                                                              0);
                        return (DDERETURN)handle;
                    }
                }
                break;
            }

        case XTYP_POKE:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection)
                {
                    wxString item_name = DDEStringFromAtom(hsz2);

                    DWORD len = DdeGetData(hData, NULL, 0, 0);

                    void *data = connection->GetBufferAtLeast(len);
                    wxASSERT_MSG(data != NULL,
                                 wxT("Buffer too small in _DDECallback (XTYP_POKE)") );

                    DdeGetData(hData, (LPBYTE)data, len, 0);

                    DdeFreeDataHandle(hData);

                    connection->OnPoke(connection->m_topicName,
                                       item_name,
                                       data,
                                       (int)len,
                                       (wxIPCFormat) wFmt);

                    return (DDERETURN)DDE_FACK;
                }
                else
                {
                    return (DDERETURN)DDE_FNOTPROCESSED;
                }
            }

        case XTYP_ADVSTART:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection)
                {
                    wxString item_name = DDEStringFromAtom(hsz2);

                    return (DDERETURN)connection->
                                OnStartAdvise(connection->m_topicName, item_name);
                }

                break;
            }

        case XTYP_ADVSTOP:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection)
                {
                    wxString item_name = DDEStringFromAtom(hsz2);

                    return (DDERETURN)connection->
                        OnStopAdvise(connection->m_topicName, item_name);
                }

                break;
            }

        case XTYP_ADVREQ:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection && connection->m_sendingData)
                {
                    HDDEDATA data = DdeCreateDataHandle
                                    (
                                        DDEIdInst,
                                        (LPBYTE)connection->m_sendingData,
                                        connection->m_dataSize,
                                        0,
                                        hsz2,
                                        connection->m_dataType,
                                        0
                                    );

                    connection->m_sendingData = NULL;

                    return (DDERETURN)data;
                }

                break;
            }

        case XTYP_ADVDATA:
            {
                wxDDEConnection *connection = DDEFindConnection(hConv);

                if (connection)
                {
                    wxString item_name = DDEStringFromAtom(hsz2);

                    DWORD len = DdeGetData(hData, NULL, 0, 0);

                    void *data = connection->GetBufferAtLeast(len);
                    wxASSERT_MSG(data != NULL,
                                 wxT("Buffer too small in _DDECallback (XTYP_ADVDATA)") );

                    DdeGetData(hData, (LPBYTE)data, len, 0);

                    DdeFreeDataHandle(hData);
                    if ( connection->OnAdvise(connection->m_topicName,
                                              item_name,
                                              data,
                                              (int)len,
                                              (wxIPCFormat) wFmt) )
                    {
                        return (DDERETURN)(DWORD)DDE_FACK;
                    }
                }

                return (DDERETURN)DDE_FNOTPROCESSED;
            }
    }

    return (DDERETURN)0;
}

// ----------------------------------------------------------------------------
// DDE strings and atoms
// ----------------------------------------------------------------------------

// Atom table stuff
static HSZ DDEAddAtom(const wxString& str)
{
    HSZ atom = DDEAtomFromString(str);
    wxAtomTable[str] = atom;
    return atom;
}

static HSZ DDEGetAtom(const wxString& str)
{
    wxAtomMap::iterator it = wxAtomTable.find(str);

    if (it != wxAtomTable.end())
        return it->second;

    return DDEAddAtom(str);
}

/* atom <-> strings
The returned handle has to be freed by the caller (using
(static) DDEFreeString).
*/
static HSZ DDEAtomFromString(const wxString& s)
{
    wxASSERT_MSG( DDEIdInst, wxT("DDE not initialized") );

    HSZ hsz = DdeCreateStringHandle(DDEIdInst, wxMSW_CONV_LPTSTR(s), DDE_CP);
    if ( !hsz )
    {
        DDELogError(_("Failed to create DDE string"));
    }

    return hsz;
}

static wxString DDEStringFromAtom(HSZ hsz)
{
    // all DDE strings are normally limited to 255 bytes
    static const size_t len = 256;

    wxString s;
    (void)DdeQueryString(DDEIdInst, hsz, wxStringBuffer(s, len), len, DDE_CP);

    return s;
}

static void DDEFreeString(HSZ hsz)
{
    // DS: Failure to free a string handle might indicate there's
    // some other severe error.
    bool ok = (::DdeFreeStringHandle(DDEIdInst, hsz) != 0);
    wxASSERT_MSG( ok, wxT("Failed to free DDE string handle") );
    wxUnusedVar(ok);
}

// ----------------------------------------------------------------------------
// error handling
// ----------------------------------------------------------------------------

static void DDELogError(const wxString& s, UINT error)
{
    if ( !error )
    {
        error = DdeGetLastError(DDEIdInst);
    }

    wxLogError(s + wxT(": ") + DDEGetErrorMsg(error));
}

static wxString DDEGetErrorMsg(UINT error)
{
    wxString err;
    switch ( error )
    {
        case DMLERR_NO_ERROR:
            err = _("no DDE error.");
            break;

        case DMLERR_ADVACKTIMEOUT:
            err = _("a request for a synchronous advise transaction has timed out.");
            break;
        case DMLERR_BUSY:
            err = _("the response to the transaction caused the DDE_FBUSY bit to be set.");
            break;
        case DMLERR_DATAACKTIMEOUT:
            err = _("a request for a synchronous data transaction has timed out.");
            break;
        case DMLERR_DLL_NOT_INITIALIZED:
            err = _("a DDEML function was called without first calling the DdeInitialize function,\nor an invalid instance identifier\nwas passed to a DDEML function.");
            break;
        case DMLERR_DLL_USAGE:
            err = _("an application initialized as APPCLASS_MONITOR has\nattempted to perform a DDE transaction,\nor an application initialized as APPCMD_CLIENTONLY has \nattempted to perform server transactions.");
            break;
        case DMLERR_EXECACKTIMEOUT:
            err = _("a request for a synchronous execute transaction has timed out.");
            break;
        case DMLERR_INVALIDPARAMETER:
            err = _("a parameter failed to be validated by the DDEML.");
            break;
        case DMLERR_LOW_MEMORY:
            err = _("a DDEML application has created a prolonged race condition.");
            break;
        case DMLERR_MEMORY_ERROR:
            err = _("a memory allocation failed.");
            break;
        case DMLERR_NO_CONV_ESTABLISHED:
            err = _("a client's attempt to establish a conversation has failed.");
            break;
        case DMLERR_NOTPROCESSED:
            err = _("a transaction failed.");
            break;
        case DMLERR_POKEACKTIMEOUT:
            err = _("a request for a synchronous poke transaction has timed out.");
            break;
        case DMLERR_POSTMSG_FAILED:
            err = _("an internal call to the PostMessage function has failed. ");
            break;
        case DMLERR_REENTRANCY:
            err = _("reentrancy problem.");
            break;
        case DMLERR_SERVER_DIED:
            err = _("a server-side transaction was attempted on a conversation\nthat was terminated by the client, or the server\nterminated before completing a transaction.");
            break;
        case DMLERR_SYS_ERROR:
            err = _("an internal error has occurred in the DDEML.");
            break;
        case DMLERR_UNADVACKTIMEOUT:
            err = _("a request to end an advise transaction has timed out.");
            break;
        case DMLERR_UNFOUND_QUEUE_ID:
            err = _("an invalid transaction identifier was passed to a DDEML function.\nOnce the application has returned from an XTYP_XACT_COMPLETE callback,\nthe transaction identifier for that callback is no longer valid.");
            break;
        default:
            err.Printf(_("Unknown DDE error %08x"), error);
    }

    return err;
}

#endif
  // wxUSE_IPC
