package io.jeffrey.play.http;

import io.jeffrey.play.client.ClientFactory;
import io.jeffrey.play.client.ConnectionEvents;
import io.jeffrey.play.client.framing.Codec;
import io.jeffrey.play.client.framing.Interceptor;
import io.jeffrey.play.client.framing.Receptor;
import io.jeffrey.play.client.framing.Relay;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import org.apache.log4j.Logger;

/**
 * Connects the web socket to the server
 *
 * @author jeffrey
 *
 */
public class Pipe implements ConnectionEvents, Receptor {
    private static final Logger LOG         = Logger.getLogger(Pipe.class);
    private final ClientFactory clientFactory;
    private Channel             playChannel = null;
    private String              tableId     = null;
    private final Channel       webChannel;
    private final Relay         webRelay;

    /**
     * @param clientFactory how we connect to the play service
     * @param webChannel the web socket
     */
    public Pipe(final ClientFactory clientFactory, final SocketChannel webChannel) {
        this.clientFactory = clientFactory;
        this.webChannel = webChannel;

        final Relay writeOnlyIfPlayChannel = new Relay(new Receptor() {

            public void frame(String frame) {
                playChannel.writeAndFlush(Unpooled.copiedBuffer(frame.getBytes()));
            }

        });

        // once connected, we will
        final Receptor spool = new Receptor() {
            public void frame(final String frame) {
                writeOnlyIfPlayChannel.relay(frame, playChannel != null);
            }
        };

        final Interceptor intercept = new Interceptor(spool) {
            @Override
            public boolean intercept(final String command, final String data, final boolean valid) {
                if (command.equals("CONNCONN")) {
                    connect(data);
                    return false;
                }
                return playChannel != null;
            }
        };

        webRelay = new Relay(intercept);
    }

    /**
     * write data back to the websocket client
     */
    public void clientWrite(final String msg) {
        LOG.info("writing:" + msg);
        webRelay.relay(msg, true);
    }

    /**
     * connect the client to the given table
     *
     * @param tableId
     */
    private void connect(final String tableId) {
        LOG.info("connecting:");
        this.tableId = tableId;
        // TODO: resolve the table into an IP
        clientFactory.connect("127.0.0.1", 5555, this, this);
    }

    /**
     * the client finally connected, let's finish the pipe
     * 
     * @param playChannel the channel connected to the play service
     */
    public void connected(final Channel playChannel) {
        LOG.info("client connected to: " + playChannel + " on:" + tableId);
        this.playChannel = playChannel;
        this.playChannel.writeAndFlush(Unpooled.copiedBuffer(Codec.wrap("SUBSCRIB", tableId).getBytes()));
        webRelay.relay("", true);
        LOG.info("subscribed");
    }

    /**
     * we failed to connect, so we report it here and disconnect the client
     */
    public void failed(final String host, final int port, final Exception e) {
        LOG.error("lost connection to:" + host + ":" + port, e);
        webChannel.close();
    }

    /**
     * a complete frame has arrived from the play service, let's relay it back up
     */
    public void frame(final String frame) {
        LOG.info("incoming frame:" + frame);
        if (frame == null) {
            terminate();
        } else {
            webChannel.writeAndFlush(new TextWebSocketFrame(frame));
        }
    }

    /**
     * the web socket has terminate, let's disconnect from the play service
     */
    public void terminate() {
        if (playChannel != null) {
            playChannel.close();
            playChannel = null;
        }
        if (webChannel != null) {
            webChannel.close();
        }
    }
}
