/*
 * Copyright (c) 2011-2015, Dan McNulty
 * All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package net.udidb.server.driver;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.inject.Inject;

import io.vertx.core.Handler;
import io.vertx.core.http.ServerWebSocket;
import io.vertx.core.http.WebSocketFrame;
import ws.wamp.jawampa.WampError;
import ws.wamp.jawampa.WampMessages;
import ws.wamp.jawampa.WampMessages.WampMessage;
import ws.wamp.jawampa.WampRouter;
import ws.wamp.jawampa.WampSerialization;
import ws.wamp.jawampa.connection.IWampConnection;
import ws.wamp.jawampa.connection.IWampConnectionListener;
import ws.wamp.jawampa.connection.IWampConnectionPromise;

/**
 * @author mcnulty
 */
public class EventsSocket implements IWampConnection
{
    private static final Logger logger = LoggerFactory.getLogger(EventsSocket.class);

    private final WampRouter wampRouter;
    private final ObjectMapper objectMapper;

    private ServerWebSocket serverWebSocket;
    private IWampConnectionListener connectionListener;

    @Inject
    public EventsSocket(WampRouter wampRouter, ObjectMapper objectMapper)
    {
        this.objectMapper = objectMapper;
        this.wampRouter = wampRouter;
    }

    public void setServerWebSocket(ServerWebSocket serverWebSocket)
    {
        this.serverWebSocket = serverWebSocket;
        this.serverWebSocket.frameHandler(frameHandler());
        this.serverWebSocket.exceptionHandler(exceptionHandler());
        this.serverWebSocket.closeHandler(closeHandler());

        connectionListener = wampRouter.connectionAcceptor().createNewConnectionListener();
        wampRouter.connectionAcceptor().acceptNewConnection(this, connectionListener);
        logger.debug("Opened WebSocket[{} <=> {}]",
                serverWebSocket.remoteAddress(),
                serverWebSocket.localAddress());

    }

    private Handler<WebSocketFrame> frameHandler()
    {
        return frame -> {
            if (frame.isBinary()) {
                throw new IllegalArgumentException("Binary messages are not supported by this server");
            }

            // TODO this doesn't handle multi-frame messages
            String message = frame.textData();

            logger.debug("[SERVER] Received WAMP message {}", message);
            passMessageToWampListener(message, objectMapper, connectionListener);
        };
    }

    private Handler<Throwable> exceptionHandler()
    {
        return cause -> {
            connectionListener.transportError(cause);
            logger.debug("Error for WebSocket[{} <=> {}]",
                    serverWebSocket.remoteAddress(),
                    serverWebSocket.localAddress(),
                    cause);
        };
    }

    private Handler<Void> closeHandler()
    {
        return (v) -> {
            connectionListener.transportClosed();
            logger.debug("Closing WebSocket[{} <=> {}]",
                    serverWebSocket.remoteAddress(),
                    serverWebSocket.localAddress());
        };
    }

    public static void passMessageToWampListener(String message, ObjectMapper objectMapper, IWampConnectionListener connectionListener)
    {
        try {
            JsonNode jsonNode = objectMapper.readTree(message);
            if (jsonNode.isArray()) {
                WampMessage wampMessage = WampMessages.WampMessage.fromObjectArray((ArrayNode)jsonNode);
                if (wampMessage == null) {
                    logger.warn("Encountered unknown WAMP message, ignoring: '{}'", message);
                }else{
                    connectionListener.messageReceived(wampMessage);
                }
            }else{
                throw new IOException("Invalid format of message, JSON array expected");
            }
        }catch (IOException | WampError e) {
            // TODO more robust handling of this may be required in the future
            throw new IllegalArgumentException("Failed to parse or process incoming message", e);
        }
    }

    @Override
    public WampSerialization serialization()
    {
        return WampSerialization.Json;
    }

    @Override
    public boolean isSingleWriteOnly()
    {
        return false;
    }

    @Override
    public void sendMessage(WampMessage message, IWampConnectionPromise<Void> promise)
    {
        try {
            if (serverWebSocket != null) {
                try {
                    String rawMessage = objectMapper.writeValueAsString(message.toObjectArray(objectMapper));
                    logger.debug("[SERVER] Sending WAMP message {}", rawMessage);
                    serverWebSocket.writeFinalTextFrame(rawMessage);
                    promise.fulfill(null);
                } catch (IOException | WampError e) {
                    logger.error("Recieved exception while sending message", e);
                    promise.reject(e);
                }
            } else {
                promise.reject(new IllegalStateException("Connection is not open"));
            }
        }
        catch (RuntimeException e)
        {
            logger.error("Received exception while sending message", e);
            promise.reject(e);
        }
    }

    @Override
    public void close(boolean sendRemaining, IWampConnectionPromise<Void> promise)
    {
        serverWebSocket.close();
    }
}

@@ -5689,20 +5689,51 @@
 et.close();%0A
+        promise.fulfill(null);%0A
     %7D%0A%7D%0A

Make sure to fulfill jawampa close promise on close.