/* Copyright 2016, Matthias Bläsing, All Rights Reserved
 *
 * The contents of this file is dual-licensed under 2
 * alternative Open Source/Free licenses: LGPL 2.1 or later and
 * Apache License 2.0. (starting with JNA version 4.0.0).
 *
 * You can freely decide which license you want to apply to
 * the project.
 *
 * You may obtain a copy of the LGPL License at:
 *
 * http://www.gnu.org/licenses/licenses.html
 *
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "LGPL2.1".
 *
 * You may obtain a copy of the Apache License at:
 *
 * http://www.apache.org/licenses/
 *
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "AL2.0".
 */
package com.sun.jna.platform.win32;

import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR;
import com.sun.jna.platform.win32.Ddeml.CONVINFO;
import com.sun.jna.platform.win32.Ddeml.HCONV;
import com.sun.jna.platform.win32.Ddeml.HDDEDATA;
import com.sun.jna.platform.win32.Ddeml.HSZ;
import com.sun.jna.platform.win32.DdemlUtil.DdeAdapter;
import com.sun.jna.platform.win32.DdemlUtil.IDdeConnection;
import com.sun.jna.platform.win32.DdemlUtil.StandaloneDdeClient;
import java.io.Closeable;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.hamcrest.core.Is.is;
import org.junit.Assert;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.BeforeClass;
import org.junit.Test;
import com.sun.jna.platform.win32.DdemlUtil.AdvstartHandler;
import com.sun.jna.platform.win32.DdemlUtil.ConnectHandler;
import com.sun.jna.platform.win32.DdemlUtil.AdvreqHandler;
import com.sun.jna.platform.win32.DdemlUtil.RequestHandler;
import com.sun.jna.platform.win32.DdemlUtil.WildconnectHandler;
import com.sun.jna.platform.win32.DdemlUtil.AdvdataHandler;
import com.sun.jna.platform.win32.DdemlUtil.ConnectConfirmHandler;
import com.sun.jna.platform.win32.DdemlUtil.DisconnectHandler;
import com.sun.jna.platform.win32.DdemlUtil.RegisterHandler;
import com.sun.jna.platform.win32.DdemlUtil.XactCompleteHandler;
import com.sun.jna.platform.win32.DdemlUtil.ExecuteHandler;
import com.sun.jna.platform.win32.DdemlUtil.PokeHandler;


public class DdemlUtilTest {
    @BeforeClass
    public static void init() {
        Logger rootLogger = Logger.getLogger("");
        rootLogger.getHandlers()[0].setLevel(Level.ALL);
        Logger.getLogger(DdeAdapter.class.getName()).setLevel(Level.FINE);
    }

    @Test
    public void testNameService() throws InterruptedException {
        final String serviceName = "TestService";
        final CountDownLatch latch = new CountDownLatch(1);
        StandaloneDdeClient client = null;
        StandaloneDdeClient server = null;

        try {
            client = new StandaloneDdeClient() {
                private final RegisterHandler registerHandler = new RegisterHandler() {
                    public void onRegister(int transactionType, HSZ baseServiceName, HSZ instanceSpecificServiceName) {
                        if (serviceName.equals(queryString(baseServiceName))) {
                            latch.countDown();
                        }
                    }
                };

                {
                    registerRegisterHandler(registerHandler);
                    this.initialize(Ddeml.APPCMD_CLIENTONLY);
                }
            };

            server = new StandaloneDdeClient() {
                {
                    this.initialize(Ddeml.APPCMD_FILTERINITS
                            | Ddeml.CBF_SKIP_ALLNOTIFICATIONS
                            | Ddeml.CBF_FAIL_ALLSVRXACTIONS);
                }
            };

            server.nameService(serviceName, Ddeml.DNS_REGISTER);

            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
        } finally {
            closeQuietly(server);
            closeQuietly(client);
        }
    }

    @Test
    public void testConnectDisconnect() throws InterruptedException {
        final String serviceName = "TestService";
        final String topicName = "TestTopic";
        final CountDownLatch connectLatch = new CountDownLatch(1);
        final CountDownLatch disconnectLatch = new CountDownLatch(1);

        StandaloneDdeClient client = null;
        StandaloneDdeClient server = null;

        try {
            client = new StandaloneDdeClient() {
                {
                    this.initialize(Ddeml.APPCMD_CLIENTONLY
                            | Ddeml.CBF_SKIP_REGISTRATIONS
                            | Ddeml.CBF_SKIP_UNREGISTRATIONS);
                }
            };
            server = new StandaloneDdeClient() {
                private final ConnectHandler connectHandler = new ConnectHandler() {
                    public boolean onConnect(int transactionType, HSZ topic, HSZ service, Ddeml.CONVCONTEXT convcontext, boolean sameInstance) {
                        return topicName.equals(queryString(topic));
                    }
                };

                private final ConnectConfirmHandler connectConfirmHandler = new ConnectConfirmHandler() {
                    public void onConnectConfirm(int transactionType, HCONV hconv, HSZ topic, HSZ service, boolean sameInstance) {
                        if (topicName.equals(queryString(topic))) {
                            connectLatch.countDown();
                        }
                    }
                };

                private final DisconnectHandler disconnectHandler = new DisconnectHandler() {
                    public void onDisconnect(int transactionType, HCONV hconv, boolean sameInstance) {
                        disconnectLatch.countDown();
                    }
                };

                {
                    registerConnectHandler(connectHandler);
                    registerConnectConfirmHandler(connectConfirmHandler);
                    registerDisconnectHandler(disconnectHandler);
                    this.initialize(Ddeml.APPCMD_FILTERINITS
                            | Ddeml.CBF_SKIP_REGISTRATIONS
                            | Ddeml.CBF_SKIP_UNREGISTRATIONS
                    );
                }
            };

            server.nameService(serviceName, Ddeml.DNS_REGISTER);


            IDdeConnection connection = client.connect(serviceName, topicName, null);
            assertTrue("Failed to connect", connectLatch.await(5, TimeUnit.SECONDS));
            connection.close();
            assertTrue("Failed to disconnect", disconnectLatch.await(5, TimeUnit.SECONDS));

        } finally {
            closeQuietly(client);
            closeQuietly(server);
        }
    }

    @Test
    public void testConnectListDisconnectListQueryNextServer() throws InterruptedException {
        final String serviceName = "TestService";
        final String topicName = "TestTopic";
        final CountDownLatch connectLatch1 = new CountDownLatch(1);
        final CountDownLatch disconnectLatch1 = new CountDownLatch(1);
        final CountDownLatch connectLatch2 = new CountDownLatch(1);
        final CountDownLatch disconnectLatch2 = new CountDownLatch(1);

        StandaloneDdeClient client = null;
        StandaloneDdeClient server1 = null;
        StandaloneDdeClient server2 = null;

        try {
            client = new StandaloneDdeClient() {
                {
                    this.initialize(Ddeml.APPCMD_CLIENTONLY
                            | Ddeml.CBF_SKIP_REGISTRATIONS
                            | Ddeml.CBF_SKIP_UNREGISTRATIONS);
                }
            };

            class Server extends StandaloneDdeClient {
                private final CountDownLatch connectLatch;
                private final CountDownLatch disconnectLatch;

                private final ConnectHandler connectHandler = new ConnectHandler() {
                    public boolean onConnect(int transactionType, HSZ topic, HSZ service, Ddeml.CONVCONTEXT convcontext, boolean sameInstance) {
                        return topicName.equals(queryString(topic));
                    }
                };

                private final ConnectConfirmHandler connectConfirmHandler = new  ConnectConfirmHandler() {
                    public void onConnectConfirm(int transactionType, Ddeml.HCONV hconv, HSZ topic, HSZ service, boolean sameInstance) {
                        if (topicName.equals(queryString(topic))) {
                            connectLatch.countDown();
                        }
                    }
                };

                private final DisconnectHandler disconnectHandler = new DisconnectHandler() {
                    public void onDisconnect(int transactionType, HCONV hconv, boolean sameInstance) {
                        disconnectLatch.countDown();
                    }
                };

                private final WildconnectHandler wildconnectHandler = new WildconnectHandler() {
                    public List<Ddeml.HSZPAIR> onWildconnect(int transactionType, HSZ topic, HSZ service, Ddeml.CONVCONTEXT convcontext, boolean sameInstance) {
                        return Collections.singletonList(new Ddeml.HSZPAIR(service, topic));
                    }
                };

                public Server(CountDownLatch connectLatch, CountDownLatch disconnectLatch) {
                    registerConnectHandler(connectHandler);
                    registerConnectConfirmHandler(connectConfirmHandler);
                    registerDisconnectHandler(disconnectHandler);
                    registerWildconnectHandler(wildconnectHandler);
                    this.initialize(Ddeml.APPCMD_FILTERINITS
                            | Ddeml.CBF_SKIP_REGISTRATIONS
                            | Ddeml.CBF_SKIP_UNREGISTRATIONS
                    );
                    this.connectLatch = connectLatch;
                    this.disconnectLatch = disconnectLatch;
                }
            };

            server1 = new Server(connectLatch1, disconnectLatch1);
            server2 = new Server(connectLatch2, disconnectLatch2);

            server1.nameService(serviceName, Ddeml.DNS_REGISTER);
            server2.nameService(serviceName, Ddeml.DNS_REGISTER);

            DdemlUtil.IDdeConnectionList connectionList = client.connectList(serviceName, topicName, null, null);

            IDdeConnection con1 = connectionList.queryNextServer(null);
            assertNotNull(con1);
            IDdeConnection con2 = connectionList.queryNextServer(con1);
            assertNotNull(con2);
            IDdeConnection con3 = connectionList.queryNextServer(con2);
            assertNull(con3);

            connectionList.close();

            assertTrue(connectLatch1.await(5, TimeUnit.SECONDS));
            assertTrue(connectLatch2.await(5, TimeUnit.SECONDS));
            assertTrue(disconnectLatch1.await(5, TimeUnit.SECONDS));
            assertTrue(disconnectLatch2.await(5, TimeUnit.SECONDS));
        } finally {
            closeQuietly(client);
            closeQuietly(server1);
            closeQuietly(server2);
        }
    }

    @Test
    public void testExecute() throws InterruptedException {
        final String serviceName = "TestService";
        final String topicName = "TestTopic";
        final String testExecute = "Execute�������";
        final CountDownLatch executeReceived = new CountDownLatch(1);

        StandaloneDdeClient client = null;
        StandaloneDdeClient server = null;

        try {
            client = new StandaloneDdeClient() {
                {
                    this.initialize(Ddeml.APPCMD_CLIENTONLY
                            | Ddeml.CBF_SKIP_REGISTRATIONS
                            | Ddeml.CBF_SKIP_UNREGISTRATIONS);
                }
            };

            server = new StandaloneDdeClient() {

                private final ConnectHandler connectHandler = new ConnectHandler() {
                    public boolean onConnect(int transactionType, HSZ topic, HSZ service, Ddeml.CONVCONTEXT convcontext, boolean sameInstance) {
                        return topicName.equals(queryString(topic));
                    }
                };

                private final ExecuteHandler executeHandler = new ExecuteHandler() {
                    public int onExecute(int transactionType, HCONV hconv, HSZ topic, Ddeml.HDDEDATA commandStringData) {
                        Pointer[] pointer = new Pointer[] { accessData(commandStringData, null) };
                        try {
                            String commandString = pointer[0].getWideString(0);
                            if(testExecute.equals(commandString) && queryString(topic).equals(topicName)) {
                                executeReceived.countDown();
                                return Ddeml.DDE_FACK;
                            }
                        } finally {
                            synchronized(pointer) {
                                unaccessData(commandStringData);
                            }
                        }
                        return Ddeml.DDE_FNOTPROCESSED;
                    }
                };

                {
                    registerConnectHandler(connectHandler);
                    registerExecuteHandler(executeHandler);
                    this.initialize(Ddeml.APPCMD_FILTERINITS
                            | Ddeml.CBF_SKIP_ALLNOTIFICATIONS
                    );
                }
            };

            server.nameService(serviceName, Ddeml.DNS_REGISTER);

            IDdeConnection con = client.connect(serviceName, topicName, null);
            con.execute(testExecute, 5 * 1000, null, null);

            assertTrue(executeReceived.await(5, TimeUnit.SECONDS));
        } finally {
            closeQuietly(client);
            closeQuietly(server);
        }
    }

    @Test
    public void testPoke() throws InterruptedException {
        final String serviceName = "TestService";
        final String topicName = "TestTopic";
        final String itemName = "TestItem";
        final String testValue = "Execute�������";
        final CountDownLatch pokeReceived = new CountDownLatch(1);

        StandaloneDdeClient client = null;
        StandaloneDdeClient server = null;

        try {
            client = new StandaloneDdeClient() {
                {
                    this.initialize(Ddeml.APPCMD_CLIENTONLY
                            | Ddeml.CBF_SKIP_REGISTRATIONS
                            | Ddeml.CBF_SKIP_UNREGISTRATIONS);
                }
            };

            server = new StandaloneDdeClient() {
                private final ConnectHandler connectHandler = new ConnectHandler() {
                    public boolean onConnect(int transactionType, HSZ topic, HSZ service, Ddeml.CONVCONTEXT convcontext, boolean sameInstance) {
                        return topicName.equals(queryString(topic));
                    }
                };

                private final PokeHandler pokeHandler = new PokeHandler() {
                    @Override
                    public int onPoke(int transactionType, int dataFormat, HCONV hconv, HSZ topic, HSZ item, Ddeml.HDDEDATA hdata) {
                        Pointer[] pointer = new Pointer[]{accessData(hdata, null)};
                        try {
                            String commandString = pointer[0].getWideString(0);
                            if (testValue.equals(commandString) && queryString(topic).equals(topicName) && queryString(item).equals(itemName)) {
                                pokeReceived.countDown();
                                return Ddeml.DDE_FACK;
                            }
                        } finally {
                            synchronized (pointer) {
                            }
                        }
                        return Ddeml.DDE_FNOTPROCESSED;
                    }
                };

                {
                    registerConnectHandler(connectHandler);
                    registerPokeHandler(pokeHandler);
                    this.initialize(Ddeml.APPCMD_FILTERINITS
                            | Ddeml.CBF_SKIP_ALLNOTIFICATIONS
                    );
                }
            };

            server.nameService(serviceName, Ddeml.DNS_REGISTER);

            IDdeConnection con = client.connect(serviceName, topicName, null);
            Memory mem = new Memory( (testValue.length() + 1 ) * 2);
            mem.setWideString(0, testValue);
            con.poke(mem, (int) mem.size(), itemName, WinUser.CF_UNICODETEXT, 5 * 1000, null, null);

            assertTrue(pokeReceived.await(5, TimeUnit.SECONDS));
        } finally {
            closeQuietly(client);
            closeQuietly(server);
        }
    }

    @Test
    public void testRequest() throws InterruptedException {
        final String serviceName = "TestService";
        final String topicName = "TestTopic";
        final String itemName = "TestItem";
        final String testValue = "Execute�������";
        final CountDownLatch pokeReceived = new CountDownLatch(1);

        StandaloneDdeClient client = null;
        StandaloneDdeClient server = null;

        try {
            client = new StandaloneDdeClient() {
                {
                    this.initialize(Ddeml.APPCMD_CLIENTONLY
                            | Ddeml.CBF_SKIP_REGISTRATIONS
                            | Ddeml.CBF_SKIP_UNREGISTRATIONS);
                }
            };

            server = new StandaloneDdeClient() {
                private final ConnectHandler connectHandler = new ConnectHandler() {
                    public boolean onConnect(int transactionType, HSZ topic, HSZ service, Ddeml.CONVCONTEXT convcontext, boolean sameInstance) {
                        return topicName.equals(queryString(topic));
                    }
                };

                private final RequestHandler requestHandler = new RequestHandler() {
                    public HDDEDATA onRequest(int transactionType, int dataFormat, HCONV hconv, HSZ topic, HSZ item) {
                        if (dataFormat == WinUser.CF_UNICODETEXT && queryString(topic).equals(topicName) && queryString(item).equals(itemName)) {
                            Memory mem = new Memory((testValue.length() + 1) * 2);
                            mem.setWideString(0, testValue);
                            HDDEDATA result = createDataHandle(mem, (int) mem.size(), 0, item, dataFormat, 0);
                            pokeReceived.countDown();
                            return result;
                        } else {
                            return null;
                        }
                    }
                };

                {
                    registerConnectHandler(connectHandler);
                    registerRequestHandler(requestHandler);
                    this.initialize(Ddeml.APPCMD_FILTERINITS
                            | Ddeml.CBF_SKIP_ALLNOTIFICATIONS
                    );
                }
            };

            server.nameService(serviceName, Ddeml.DNS_REGISTER);

            IDdeConnection con = client.connect(serviceName, topicName, null);
            HDDEDATA data = con.request(itemName, WinUser.CF_UNICODETEXT, 5 * 1000, null, null);
            try {
                try {
                    Pointer pointer = server.accessData(data, null);
                    assertThat(pointer.getWideString(0), is(testValue));
                } finally {
                    server.unaccessData(data);
                }
            } finally {
                server.freeDataHandle(data);
            }

            assertTrue(pokeReceived.await(5, TimeUnit.SECONDS));
        } finally {
            closeQuietly(client);
            closeQuietly(server);
        }
    }

    @Test
    public void testAbandonTransaction() throws InterruptedException {
        final String serviceName = "TestService";
        final String topicName = "TestTopic";
        final String testExecute = "Execute�������";
        final CountDownLatch allTransactionsInvoked = new CountDownLatch(1);
        final CountDownLatch executesProcessed = new CountDownLatch(3);

        StandaloneDdeClient client = null;
        StandaloneDdeClient server = null;

        try {
            client = new StandaloneDdeClient() {
                private final XactCompleteHandler xactCompleteHandler = new XactCompleteHandler() {
                    public void onXactComplete(int transactionType, int dataFormat, HCONV hConv, HSZ topic, HSZ item, HDDEDATA hdata, ULONG_PTR transactionIdentifier, ULONG_PTR statusFlag) {
                        executesProcessed.countDown();
                    }
                };

                {
                    registerXactCompleteHandler(xactCompleteHandler);
                    this.initialize(Ddeml.APPCMD_CLIENTONLY
                            | Ddeml.CBF_SKIP_REGISTRATIONS
                            | Ddeml.CBF_SKIP_UNREGISTRATIONS);
                }
            };

            server = new StandaloneDdeClient() {
                private final ConnectHandler connectHandler = new ConnectHandler() {
                    public boolean onConnect(int transactionType, HSZ topic, HSZ service, Ddeml.CONVCONTEXT convcontext, boolean sameInstance) {
                        return topicName.equals(queryString(topic));
                    }
                };

                private final ExecuteHandler executeHandler = new ExecuteHandler() {
                    public int onExecute(int transactionType, HCONV hconv, HSZ topic, Ddeml.HDDEDATA commandStringData) {
                        try {
                            if(! allTransactionsInvoked.await(5, TimeUnit.SECONDS)) {
                                return Ddeml.DDE_FNOTPROCESSED;
                            }
                            Pointer[] pointer = new Pointer[] { accessData(commandStringData, null) };
                            try {
                                String commandString = pointer[0].getWideString(0);
                                if(testExecute.equals(commandString) && queryString(topic).equals(topicName)) {
                                    return Ddeml.DDE_FACK;
                                }
                            } finally {
                                synchronized(pointer) {
                                    unaccessData(commandStringData);
                                }
                            }
                            return Ddeml.DDE_FNOTPROCESSED;
                        }   catch (InterruptedException ex) {
                            Logger.getLogger(DdemlUtilTest.class.getName()).log(Level.SEVERE, null, ex);
                            return Ddeml.DDE_FNOTPROCESSED;
                        }
                    }
                };

                {
                    registerConnectHandler(connectHandler);
                    registerExecuteHandler(executeHandler);
                    this.initialize(Ddeml.APPCMD_FILTERINITS
                            | Ddeml.CBF_SKIP_ALLNOTIFICATIONS
                    );
                }
            };

            server.nameService(serviceName, Ddeml.DNS_REGISTER);

            IDdeConnection con = client.connect(serviceName, topicName, null);

            WinDef.DWORDByReference result = new WinDef.DWORDByReference();
            con.execute(testExecute, Ddeml.TIMEOUT_ASYNC, result, null);
            con.execute(testExecute, Ddeml.TIMEOUT_ASYNC, result, null);
            int transactionId2 = result.getValue().intValue();
            con.execute(testExecute, Ddeml.TIMEOUT_ASYNC, result, null);

            con.abandonTransaction(transactionId2);

            allTransactionsInvoked.countDown();

            assertFalse(executesProcessed.await(2, TimeUnit.SECONDS));
            assertThat(executesProcessed.getCount(), is(1L));
        } finally {
            closeQuietly(client);
            closeQuietly(server);
        }
    }

    @Test
    public void testEnableCallback() throws InterruptedException {
        final String serviceName = "TestService";
        final String topicName = "TestTopic";
        final String testExecute = "Execute�������";
        final CountDownLatch executesProcessed = new CountDownLatch(3);

        StandaloneDdeClient client = null;
        StandaloneDdeClient server = null;

        try {
            client = new StandaloneDdeClient() {
                private final XactCompleteHandler xactCompleteHandler = new XactCompleteHandler() {
                    public void onXactComplete(int transactionType, int dataFormat, HCONV hConv, HSZ topic, HSZ item, HDDEDATA hdata, ULONG_PTR transactionIdentifier, ULONG_PTR statusFlag) {
                        executesProcessed.countDown();
                    }

                };

                {
                    registerXactCompleteHandler(xactCompleteHandler);
                    this.initialize(Ddeml.APPCMD_CLIENTONLY
                            | Ddeml.CBF_SKIP_REGISTRATIONS
                            | Ddeml.CBF_SKIP_UNREGISTRATIONS);
                }
            };

            server = new StandaloneDdeClient() {
                private final ConnectHandler connectHandler = new ConnectHandler() {
                    public boolean onConnect(int transactionType, HSZ topic, HSZ service, Ddeml.CONVCONTEXT convcontext, boolean sameInstance) {
                        return topicName.equals(queryString(topic));
                    }
                };


                private final ExecuteHandler executeHandler = new ExecuteHandler() {
                    public int onExecute(int transactionType, HCONV hconv, HSZ topic, Ddeml.HDDEDATA commandStringData) {
                        Pointer[] pointer = new Pointer[] { accessData(commandStringData, null) };
                        try {
                            String commandString = pointer[0].getWideString(0);
                            if(testExecute.equals(commandString) && queryString(topic).equals(topicName)) {
                                return Ddeml.DDE_FACK;
                            }
                        } finally {
                            synchronized(pointer) {
                                unaccessData(commandStringData);
                            }
                        }
                        return Ddeml.DDE_FNOTPROCESSED;
                    }
                };

                {
                    registerConnectHandler(connectHandler);
                    registerExecuteHandler(executeHandler);
                    this.initialize(Ddeml.APPCMD_FILTERINITS
                            | Ddeml.CBF_SKIP_ALLNOTIFICATIONS
                    );
                }
            };

            server.nameService(serviceName, Ddeml.DNS_REGISTER);
            server.enableCallback(Ddeml.EC_DISABLE);

            assertThat(server.enableCallback(Ddeml.EC_QUERYWAITING), is(false));

            IDdeConnection con = client.connect(serviceName, topicName, null);

            WinDef.DWORDByReference result = new WinDef.DWORDByReference();
            con.execute(testExecute, Ddeml.TIMEOUT_ASYNC, result, null);
            con.execute(testExecute, Ddeml.TIMEOUT_ASYNC, result, null);
            con.execute(testExecute, Ddeml.TIMEOUT_ASYNC, result, null);

            assertThat(server.enableCallback(Ddeml.EC_QUERYWAITING), is(true));

            server.enableCallback(Ddeml.EC_ENABLEALL);

            assertTrue(executesProcessed.await(3, TimeUnit.SECONDS));
        } finally {
            closeQuietly(client);
            closeQuietly(server);
        }
    }

    @Test
    public void testQueryConvInfoSetUserHandle() throws InterruptedException {
        final String serviceName = "TestService";
        final String topicName = "TestTopic";
        final String testExecute = "Execute�������";
        final CountDownLatch executesProcessed = new CountDownLatch(1);

        StandaloneDdeClient client = null;
        StandaloneDdeClient server = null;

        try {
            client = new StandaloneDdeClient() {
                private final XactCompleteHandler xactCompleteHandler = new XactCompleteHandler() {
                    public void onXactComplete(int transactionType, int dataFormat, HCONV hConv, HSZ topic, HSZ item, HDDEDATA hdata, ULONG_PTR transactionIdentifier, ULONG_PTR statusFlag) {
                        CONVINFO convInfo = wrap(hConv).queryConvInfo(transactionIdentifier.intValue());
                        if(convInfo.hUser.intValue() == 42) {
                            executesProcessed.countDown();
                        }
                    }

                };

                {
                    registerXactCompleteHandler(xactCompleteHandler);
                    this.initialize(Ddeml.APPCMD_CLIENTONLY
                            | Ddeml.CBF_SKIP_REGISTRATIONS
                            | Ddeml.CBF_SKIP_UNREGISTRATIONS);
                }
            };

            server = new StandaloneDdeClient() {
                private final ConnectHandler connectHandler = new ConnectHandler() {
                    public boolean onConnect(int transactionType, HSZ topic, HSZ service, Ddeml.CONVCONTEXT convcontext, boolean sameInstance) {
                        return topicName.equals(queryString(topic));
                    }
                };

                private final ExecuteHandler executeHandler = new ExecuteHandler() {
                    public int onExecute(int transactionType, HCONV hconv, HSZ topic, Ddeml.HDDEDATA commandStringData) {
                        Pointer[] pointer = new Pointer[] { accessData(commandStringData, null) };
                        try {
                            String commandString = pointer[0].getWideString(0);
                            if(testExecute.equals(commandString) && queryString(topic).equals(topicName)) {
                                return Ddeml.DDE_FACK;
                            }
                        } finally {
                            synchronized(pointer) {
                                unaccessData(commandStringData);
                            }
                        }
                        return Ddeml.DDE_FNOTPROCESSED;
                    }
                };

                {
                    registerConnectHandler(connectHandler);
                    registerExecuteHandler(executeHandler);
                    this.initialize(Ddeml.APPCMD_FILTERINITS
                            | Ddeml.CBF_SKIP_ALLNOTIFICATIONS
                    );
                }
            };

            server.nameService(serviceName, Ddeml.DNS_REGISTER);

            IDdeConnection con = client.connect(serviceName, topicName, null);

            con.execute(testExecute, Ddeml.TIMEOUT_ASYNC, null, new BaseTSD.DWORD_PTR(42L));

            assertTrue(executesProcessed.await(3, TimeUnit.SECONDS));
        } finally {
            closeQuietly(client);
            closeQuietly(server);
        }
    }

    @Test
    public void testAdvise() throws InterruptedException {
        final String serviceName = "TestService";
        final String topicName = "TestTopic";
        final String itemName = "TestItem";
        final String testValue = "Execute�������";
        final CountDownLatch adviseStartReceived = new CountDownLatch(1);
        final CountDownLatch adviseDataRequestReceived = new CountDownLatch(1);
        final CountDownLatch adviseDataReceived = new CountDownLatch(1);

        StandaloneDdeClient client = null;
        StandaloneDdeClient server = null;

        try {
            client = new StandaloneDdeClient() {
                private final AdvdataHandler advdataHandler = new DdemlUtil.AdvdataHandler() {
                    public int onAdvdata(int transactionType, int dataFormat, HCONV hconv, HSZ topic, HSZ item, HDDEDATA hdata) {
                        if (dataFormat == WinUser.CF_UNICODETEXT
                                && topicName.equals(queryString(topic))
                                && itemName.equals(queryString(item))) {
                            Pointer pointer = accessData(hdata, null);
                            try {
                                if (testValue.equals(pointer.getWideString(0))) {
                                    adviseDataReceived.countDown();
                                }
                            } finally {
                                unaccessData(hdata);
                            }
                        }
                        return Ddeml.DDE_FACK;
                    }
                };

                {
                    registerAdvdataHandler(advdataHandler);
                    this.initialize(Ddeml.APPCMD_CLIENTONLY
                            | Ddeml.CBF_SKIP_REGISTRATIONS
                            | Ddeml.CBF_SKIP_UNREGISTRATIONS);
                }
            };

            server = new StandaloneDdeClient() {
                private final ConnectHandler connectHandler = new ConnectHandler() {
                    public boolean onConnect(int transactionType, HSZ topic, HSZ service, Ddeml.CONVCONTEXT convcontext, boolean sameInstance) {
                        return topicName.equals(queryString(topic));
                    }
                };

                private final AdvreqHandler advreqHandler = new AdvreqHandler() {
                    public HDDEDATA onAdvreq(int transactionType, int dataFormat, HCONV hconv, HSZ topic, HSZ item, int count) {
                        adviseDataRequestReceived.countDown();
                        Memory mem = new Memory( (testValue.length() + 1) * 2);
                        mem.setWideString(0, testValue);
                        return createDataHandle(mem, (int) mem.size(), 0, item, dataFormat, 0);
                    }
                };

                private final AdvstartHandler advstartHandler = new AdvstartHandler() {
                    public boolean onAdvstart(int transactionType, int dataFormat, HCONV hconv, HSZ topic, HSZ item) {
                        adviseStartReceived.countDown();
                        return dataFormat == WinUser.CF_UNICODETEXT
                                && topicName.equals(queryString(topic))
                                && itemName.equals(queryString(item));
                    }
                };

                {
                    registerConnectHandler(connectHandler);
                    registerAdvReqHandler(advreqHandler);
                    registerAdvstartHandler(advstartHandler);
                    this.initialize(Ddeml.APPCMD_FILTERINITS
                            | Ddeml.CBF_SKIP_ALLNOTIFICATIONS
                    );
                }
            };

            server.nameService(serviceName, Ddeml.DNS_REGISTER);

            IDdeConnection con = client.connect(serviceName, topicName, null);
            con.advstart(itemName, WinUser.CF_UNICODETEXT, 5 * 1000, null, null);

            assertTrue(adviseStartReceived.await(5, TimeUnit.SECONDS));

            server.postAdvise(topicName, itemName);

            assertTrue(adviseDataRequestReceived.await(5, TimeUnit.SECONDS));
            assertTrue(adviseDataReceived.await(5, TimeUnit.SECONDS));
        } finally {
            closeQuietly(client);
            closeQuietly(server);
        }
    }

    private static void closeQuietly(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (Exception ex) {
            }
        }
    }
}
