package org.hdl.anima.remoting.netty;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.hdl.anima.AppConf;
import org.hdl.anima.common.NamedThreadFactory;
import org.hdl.anima.common.utils.StringUtils;
import org.hdl.anima.remoting.Channel;
import org.hdl.anima.remoting.ChannelHandler;
import org.hdl.anima.remoting.Codec;
import org.hdl.anima.remoting.Constants;
import org.hdl.anima.remoting.RemotingException;
import org.hdl.anima.remoting.dispatcher.ChannelHandlers;
import org.hdl.anima.remoting.support.AbstractServer;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * NettyServer
 * @author qiuhd
 * @since  2014-8-1
 * @version V1.0.0
 */
public class NettyServer extends AbstractServer {
	
	private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);

	private ServerBootstrap   bootstrap;
	
	private Map<String, Channel> channels ;
	
	private org.jboss.netty.channel.Channel channel;
	
	public NettyServer(AppConf conf, ChannelHandler handler, Codec codec) throws RemotingException {
		super(conf, wrapChannelHandler(conf,handler),codec);
	}
	
	protected static ChannelHandler wrapChannelHandler(AppConf conf, ChannelHandler handler){
		String serverId = conf.get(Constants.SERVER_ID,"");
		String threadPoolName = SERVER_THREAD_POOL_NAME;
		if (!StringUtils.isEmpty(serverId)) {
			threadPoolName = SERVER_THREAD_POOL_NAME + "[" + serverId+"]";
		}
		conf.set(Constants.THREAD_NAME_KEY, threadPoolName);
		return ChannelHandlers.wrap(handler, conf);
	}
	
	@Override
	public void doOpen() throws Throwable {
	    ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", false));
        ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
        int ioThread = conf.getInt(Constants.IO_THREADS,Constants.DEFAULT_IO_THREADS);
        ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, ioThread);
        bootstrap = new ServerBootstrap(channelFactory);
        
        final NettyHandler nettyHandler = new NettyHandler(getConf(), this);
        channels = nettyHandler.getChannels();
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() {
                NettyCodecAdapter adapter = new NettyCodecAdapter(conf,getCodec(), NettyServer.this);
                ChannelPipeline pipeline = Channels.pipeline();
                pipeline.addLast("decoder", adapter.getDecoder());
                pipeline.addLast("encoder", adapter.getEncoder());
                pipeline.addLast("handler", nettyHandler);
                return pipeline;
            }
        });
        // bind
        channel = bootstrap.bind(getBindAddress());
	}

	@Override
	public void doClose() throws Throwable {
		try {
            if (channel != null) {
                // unbind.
                channel.close();
            }
        } catch (Throwable e) {
            logger.warn(e.getMessage(), e);
        }
        try {
            Collection<Channel> channels = getChannels();
            if (channels != null && channels.size() > 0) {
                for (Channel channel : channels) {
                    try {
                        channel.close();
                    } catch (Throwable e) {
                        logger.warn(e.getMessage(), e);
                    }
                }
            }
        } catch (Throwable e) {
            logger.warn(e.getMessage(), e);
        }
        try {
            if (bootstrap != null) { 
                // release external resource.
                bootstrap.releaseExternalResources();
            }
        } catch (Throwable e) {
            logger.warn(e.getMessage(), e);
        }
        try {
            if (channels != null) {
                channels.clear();
            }
        } catch (Throwable e) {
            logger.warn(e.getMessage(), e);
        }
	}

	@Override
	public boolean isBound() {
		return channel.isBound();
	}

	@Override
	public Collection<Channel> getChannels() {
		return this.channels.values() ;
//		Collection<Channel> chs = new HashSet<Channel>();
//		for (Channel channel : this.channels.values()) {
//			if (channel.isConnected()) {
//				chs.add(channel);
//			} else {
//				InetSocketAddress address = (InetSocketAddress) channel
//						.getRemoteAddress();
//				String key = address.getAddress().getHostAddress() + ":"
//						+ address.getPort();
//				channels.remove(key);
//			}
//		}
//		return chs;
	}
}