package com.hmsoft.remote.rmi.codec;

import java.io.IOException;
import java.util.List;
import com.hmsoft.remote.rmi.rpc.NettyBackedChannelBuffer;
import com.hmsoft.remote.rmi.transport.NettyChannel;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * 
 * @author zq
 *
 */
public class NettyCodecAdapter {

    private final String url;

    private final Codec upstreamCodec;
    
    private final Codec downStreamCodec;
    
    private final com.hmsoft.remote.rmi.handler.ChannelHandler handler;

    private final ChannelHandler encoder = new InternalEncoder();

    private final ChannelHandler decoder = new InternalDecoder();
    
    public NettyCodecAdapter(Codec upstreamCodec, Codec downStreamCodec, String url, com.hmsoft.remote.rmi.handler.ChannelHandler handler) {
        this.upstreamCodec = upstreamCodec;
        this.downStreamCodec = downStreamCodec;
        this.url = url;
        this.handler = handler;
    }
    
    private class InternalEncoder extends MessageToByteEncoder<Object> {
    	@Override
    	protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
    		com.hmsoft.remote.rmi.buffer.ChannelBuffer buffer = new NettyBackedChannelBuffer(out);
    		io.netty.channel.Channel ch = ctx.channel();
            NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler);
            try {
            	upstreamCodec.encode(channel, buffer, msg);
            } finally {
                NettyChannel.removeChannelIfDisconnected(ch);
            }
    	}
    }
    
    private class InternalDecoder extends ByteToMessageDecoder {

        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf input, List<Object> out) throws Exception {

        	com.hmsoft.remote.rmi.buffer.ChannelBuffer message = new NettyBackedChannelBuffer(input);

            NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);

            Object msg = null;

            int saveReaderIndex;

            try {
                // decode object.
                do {
                    saveReaderIndex = message.readerIndex();
                    try {
                        msg = downStreamCodec.decode(channel, message);
                    } catch (IOException e) {
                        throw e;
                    }
                    if (msg == Codec.DecodeResult.NEED_MORE_INPUT) {
                        message.readerIndex(saveReaderIndex);
                        break;
                    } else {
                        //is it possible to go here ?
                        if (saveReaderIndex == message.readerIndex()) {
                            throw new IOException("Decode without read data.");
                        }
                        if (msg != null) {
                            out.add(msg);
                        }
                    }
                } while (message.readable());
            } finally {
                NettyChannel.removeChannelIfDisconnected(ctx.channel());
            }
        }
    }
    
    public ChannelHandler getEncoder() {
        return encoder;
    }

    public ChannelHandler getDecoder() {
        return decoder;
    }

}
