package com.hmsoft.remote.rmi.transport;

import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.hmsoft.remote.rmi.exception.RemotingException;
import com.hmsoft.remote.rmi.handler.ChannelHandler;
import com.hmsoft.remote.rmi.rpc.AbstractPeer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;

/**
 * 包装后的channnel.
 * @author zq
 *
 */
public class NettyChannel extends AbstractPeer {

	//持有netty的io.netty.channel.Channel
    private final Channel channel;
    private volatile boolean closed;
    private int timeout = 3000;//TODO...
    private static final ConcurrentMap<Channel, NettyChannel> channelMap = new ConcurrentHashMap<Channel, NettyChannel>();
    
    private NettyChannel(Channel channel, String url, ChannelHandler handler) {
        super(url, handler);
        if (channel == null) {
            throw new IllegalArgumentException("netty channel == null;");
        }
        this.channel = channel;
    }
    
    /**
     * 发送不等待.
     * @param message
     * @throws RemotingException
     */
    public void send(Object message) throws RemotingException {
        try {
            channel.writeAndFlush(message);
        } catch (Throwable e) {
            throw new RemotingException(e.getMessage());
        }
    }
    
    /**
     * 直接同步发送阻塞等结果.
     * @param message
     * @param sent, true-发送并等待超时时间,false-发送不等待结果.
     * @throws RemotingException
     */
    public void send(Object message, boolean sent) throws RemotingException {
        boolean success = true;
        try {
            ChannelFuture future = channel.writeAndFlush(message);
            if (sent) {
                success = future.await(timeout);
            }
            Throwable cause = future.cause();
            if (cause != null) {
                throw cause;
            }
        } catch (Throwable e) {
            throw new RemotingException(e.getMessage());
        }

        if (!success) {
            throw new RemotingException("Failed to send message " + message + " to  in timeout(" + timeout + "ms) limit");
        }
    }
    
    public void close() {
        try {
            removeChannelIfDisconnected(channel);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            channel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public boolean isClosed() {
        return closed;
    }
    
    public boolean isConnected() {
        return !isClosed() && channel.isActive();
    }
    
    public static void removeChannelIfDisconnected(Channel ch) {
        if (ch != null && !ch.isActive()) {
            channelMap.remove(ch);
        }
    }
    
    public static NettyChannel getOrAddChannel(Channel ch, String url, ChannelHandler handler) {
        if (ch == null) {
            return null;
        }
        NettyChannel ret = channelMap.get(ch);
        if (ret == null) {
            NettyChannel nettyChannel = new NettyChannel(ch, url, handler);
            if (ch.isActive()) {
                ret = channelMap.putIfAbsent(ch, nettyChannel);
            }
            if (ret == null) {
                ret = nettyChannel;
            }
        }
        return ret;
    }
    
    public InetSocketAddress getLocalAddress() {
        return (InetSocketAddress) channel.localAddress();
    }
    
    public InetSocketAddress getRemoteAddress() {
        return (InetSocketAddress) channel.remoteAddress();
    }
    
    
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((channel == null) ? 0 : channel.hashCode());
        return result;
    }
    
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        NettyChannel other = (NettyChannel) obj;
        if (channel == null) {
            if (other.channel != null) return false;
        } else if (!channel.equals(other.channel)) return false;
        return true;
    }

    public String toString() {
        return "NettyChannel [channel=" + channel + "]";
    }
}
