/*
 * Copyright (c) 2013, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software;Designed and Developed mainly by many Chinese 
 * opensource volunteers. you can redistribute it and/or modify it under the 
 * terms of the GNU General Public License version 2 only, as published by the
 * Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Any questions about this component can be directed to it's project Web address 
 * https://code.google.com/p/opencloudb/.
 *
 */
package com.talent.nio.communicate.util;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

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

import com.talent.nio.communicate.ChannelContext;
import com.talent.nio.communicate.ChannelContext.ConnectionState;
import com.talent.nio.communicate.monitor.vo.StatVo;
import com.talent.nio.communicate.receive.DecodeRunnable;
import com.talent.nio.communicate.receive.TcpListener;
import com.talent.nio.communicate.send.SendUtils;
import com.talent.nio.communicate.send.SendRunnable;
import com.talent.nio.connmgr.ConnectionManager;
import com.talent.nio.debug.DebugUtils;
import com.talent.nio.startup.Startup;
import com.talent.nio.utils.SystemTimer;

/**
 * 
 * 
 * @author 谭耀武 2012-1-9 上午09:29:05
 * 
 */
public class NioUtils
{
	private static final Logger log = LoggerFactory.getLogger(NioUtils.class);

	/**
	 * 
	 */
	private NioUtils()
	{

	}

	/**
	 * 给SelectionKey添加监听选项
	 * 
	 * @param key
	 * @param opt
	 */
	public static void addOpt(SelectionKey key, int opt)
	{
		if (key.isValid())
		{
			key.interestOps(key.interestOps() | opt);
		}
	}

	/**
	 * 注销掉SelectionKey的监听选项
	 * 
	 * @param key
	 * @param opt
	 */
	public static void removeOpt(SelectionKey key, int opt)
	{
		if (key.isValid())
		{
			key.interestOps(key.interestOps() & (~opt));
		}
	}

	/**
	 * 略过通道的所有数据
	 * 
	 * @param socketChannel
	 */
	public static int skipChannelData(ReadableByteChannel socketChannel) throws IOException
	{
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		int allLength = 0;
		int length = 0;
		while (socketChannel.isOpen() && (length = socketChannel.read(buffer)) > 0)
		{
			allLength += length;
			buffer.clear();
		}
		log.debug("skipped length [{}]", allLength);
		return allLength;
	}

	/**
	 * 统计发送流量
	 * 
	 * @param length
	 *            消息长度
	 * @param channelContext
	 */
	public static void statSend(int length, ChannelContext channelContext)
	{
		long sentBytes = channelContext.getStatVo().getSentBytes() + length;
		channelContext.getStatVo().setSentBytes(sentBytes);

		log.debug("{} has sent {} bytes", channelContext.getId(), sentBytes);
	}

	/**
	 * 保存注销的时间
	 */
	private static Map<ChannelContext, Long> logoutTime = new HashMap<ChannelContext, Long>();

	/**
	 * 断开连接
	 * 
	 * @param channelContext
	 * @param reason
	 * @param isComplete
	 *            是否完全彻底的注销,true:彻底地断开(所保存的统计数据将全部丢失),移除连接时必须为true;
	 */
	private static boolean _disconnect(ChannelContext channelContext, String reason, boolean isComplete)
	{
		log.warn("start disconnect {}, reason:{}, isComplete:{}", channelContext.getId(), reason, isComplete);
		SocketChannel socketChannel = channelContext.getSocketChannel();
		if (ConnectionState.LOGOUTING == channelContext.getConnectionState())
		{
//			try
//			{
//				if (socketChannel.isOpen())
//				{
//					socketChannel.close();
//				}
//			} catch (Exception e)
//			{
//				log.error("exception occured when close socketchannel:" + channelContext, e);
//			}
			return false;
		}

		synchronized (channelContext)
		{
			
//			try
//			{
//				channelContext.setConnectionState(ConnectionState.LOGOUTING);
//				if (socketChannel.isOpen())
//				{
//					socketChannel.close();
//				}
//			} catch (Exception e)
//			{
//				log.error("exception occured when close socketchannel:" + channelContext, e);
//			}
//
//			try
//			{
//				Startup.getSelector().wakeup();
//			} catch (Exception e)
//			{
//				log.error("exception occured when selector.wakeup():" + channelContext, e);
//			}

			Long lastLogoutTime = logoutTime.get(channelContext);
			long currTime = SystemTimer.currentTimeMillis();
			if (lastLogoutTime != null)
			{
				long x = currTime - lastLogoutTime;
				if ((x) < 100L)
				{
					log.error("注销得好频繁,上次注销在{}毫秒前, {} logouted at {}", x, channelContext.getId(), new Timestamp(
							lastLogoutTime).toString());
					return false;
				}
			}

			if (DebugUtils.isNeedDebug(channelContext))
			{
				log.error("may be logout, reason {}, {}", reason, channelContext);
			}
//
//			if (ConnectionState.TCP_ON == channelContext.getConnectionState())
//			{
//				log.warn("can not logout, channelContext is TCP_ON. {}", channelContext);
//				return false;
//			}

			logoutTime.put(channelContext, currTime);

			try
			{
				// 注销通道的监听-- start
				try
				{

					if (DebugUtils.isNeedDebug(channelContext))
					{
						log.warn("start logout, reason {}, {}", reason, channelContext.getId());
					}

					if (socketChannel != null)
					{
						// 注销通道的监听
						TcpListener.getMapOfSocketChannelAndChannelContext().remove(socketChannel);
						Startup.getTcpListener().logout(channelContext);
					}
				} catch (Exception e)
				{
					log.error("", e);
				}
				// 注销通道的监听-- end

				// 清空socketChannelId对应的发送队列和发送线程工厂中的缓存对象 -- start
				SendRunnable sendRunnable = channelContext.getSendRunnable();
				if (sendRunnable != null)
				{
					sendRunnable.clearMsgQueue();
					if (isComplete)
					{
						SendUtils.removeStat(channelContext);
					} else
					{
						SendUtils.recordStat(channelContext, sendRunnable.getProcessedMsgCount(),
								sendRunnable.getSubmitCount(),
								sendRunnable.getProcessedMsgByteCount());
					}
				}
				// 清空socketChannelId对应的发送队列和发送线程工厂中的缓存对象 -- end

				// 清空socketChannelId对应的接收处理队列和接收处理线程工厂中的缓存对象 -- start
				DecodeRunnable decodeRunnable = channelContext.getDecodeRunnable();
				if (decodeRunnable != null)
				{
					decodeRunnable.clearMsgQueue();
					if (isComplete)
					{
						StatUtils.removeStat(channelContext);
					} else
					{

						StatUtils.recordStat(channelContext, decodeRunnable.getProcessedMsgCount(),
								decodeRunnable.getSubmitCount(),
								decodeRunnable.getProcessedMsgByteCount());
					}
				}

			} catch (Throwable e)
			{
				log.error(" Throwable occured when logout " + channelContext.getId(), e);
				channelContext.setDesc4Err("logout fail:" + e.getMessage());
			} finally
			{
				
				if (channelContext != null)
				{
					channelContext.setConnectionState(ConnectionState.TCP_OFF);
					channelContext.setDesc4Err(reason);
				} else
				{
					log.warn(" channelContext is null");
				}
				if (DebugUtils.isNeedDebug(channelContext))
				{
					log.warn("end logout, reason {}, {}", reason, channelContext.getId());
				}
				Startup.getSelector().wakeup();
			}
			return true;
		}
	}

	/**
	 * 删除连接
	 * 
	 * @param _socketChannel
	 * @param channelContext
	 * @param reason
	 */
	public static void remove(ChannelContext channelContext, String reason)
	{
		boolean b = _disconnect(channelContext, reason, true);
		if (b)
		{
			logoutTime.remove(channelContext);
			ConnectionManager.getInstance().removeConnection(channelContext);
			channelContext.setConnectionState(ConnectionState.REMOVED);
		}

	}

	/**
	 * 断开连接
	 * 
	 * @param channelContext
	 * @param reason
	 */
	public static void disconnect(ChannelContext channelContext, String reason)
	{
		_disconnect(channelContext, reason, false);
	}

	/**
	 * 将缓冲区的数据读到字节数组中
	 * 
	 * @param buffer
	 *            不允许为null
	 * @param byteArray
	 *            不允许为null
	 */
	public static void readBufferToByteArray(ByteBuffer buffer, byte[] byteArray)
	{
		if (byteArray == null)
		{
			throw new RuntimeException("byteArray is null");
		}
		if (buffer == null)
		{
			throw new RuntimeException("buffer is null");
		}

		buffer.flip();
		buffer.get(byteArray); // 将数据从buffer读到字节数组中
	}

	private static Set<ChannelContext.ConnectionState> canSendTcpMsgStates = new HashSet<ChannelContext.ConnectionState>();

	static
	{
		canSendTcpMsgStates.add(ConnectionState.TCP_ON);
		canSendTcpMsgStates.add(ConnectionState.APP_BUILDING);
		canSendTcpMsgStates.add(ConnectionState.APP_ON);
		canSendTcpMsgStates.add(ConnectionState.APP_OFF);
		canSendTcpMsgStates.add(ConnectionState.APP_LINKFAILED);
	}

	public static boolean canSendTcpMsg(ChannelContext channelContext)
	{
		if (channelContext == null)
		{
			return false;
		} else
		{
			return canSendTcpMsgStates.contains(channelContext.getConnectionState());
		}
	}

	/**
	 * 
	 * @param channelContext
	 * @return
	 */
	public static boolean buildLink(ChannelContext channelContext)
	{
		if (channelContext == null)
		{
			log.error("channelContext is null!");
			return false;
		}

		final String ip = channelContext.getRemoteNode().getIp();
		final int port = channelContext.getRemoteNode().getPort();
		channelContext.getStatVo().getCountOfErrorPackage().set(0);// .setCountOfErrorPackage(0);

		InetSocketAddress address = new InetSocketAddress(ip, port);

		try
		{
			// 注册消息接收监听
			Startup.getTcpListener().register(address, channelContext);
		} catch (IOException e)
		{
			channelContext.getWriteIOErrorHandler().handle(null, e, channelContext,
					"exception occured when build link!");
			return false;
		}

		return true;
	}

	/**
	 * 创建统计信息
	 * 
	 * @param channelContext
	 * @return
	 */
	public static StatVo createStatInfo(ChannelContext channelContext)
	{
		StatVo ret = channelContext.getStatVo();

		int packetOgnzerQueueSize = channelContext.getDecodeRunnable().getMsgQueue().size();
		int senderQueueSize = channelContext.getSendRunnable().getMsgQueue().size();
		int packetHandlerQueueSize = channelContext.getHandlerRunnable().getMsgQueue().size();

		ret.setPacketOgnzerQueueSize(packetOgnzerQueueSize);
		ret.setSenderQueueSize(senderQueueSize);
		ret.setPacketHandlerQueueSize(packetHandlerQueueSize);

		return ret;
		//
		// int size1 =
		// channelContext.getPacketOgnzerRunnable().getMsgQueue().size();
		// int size2 =
		// channelContext.getSocketMsgSendRunnable().getMsgQueue().size();
		// int size3 =
		// channelContext.getPacketHandlerRunnable().getMsgQueue().size();
		// long size4 = channelContext.getSentBytes().longValue();
		// long size5 = channelContext.getReceivedBytes().longValue();
		//
		// StringBuilder sb = new StringBuilder();
		// sb.append(channelContext.getId()).append(StringUtil.NEWLINE);
		// sb.append("PacketOgnzer Queue:").append(size1).append(StringUtil.NEWLINE);
		// sb.append("Sender Queue:").append(size2).append(StringUtil.NEWLINE);
		// sb.append("Handler Queue:").append(size3).append(StringUtil.NEWLINE);
		// sb.append("SentBytes:").append(size4).append(StringUtil.NEWLINE);
		// sb.append("ReceivedBytes:").append(size5).append(StringUtil.NEWLINE);
		// return sb.toString();
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception
	{
		// Desktop.getDesktop().edit(new File("E:/work/2011-01/sss.dfda"));
		// Desktop.getDesktop().open(new File("E:/work/2011-01/index.php.htm"));
		Runtime.getRuntime().exec("cmd /c start E:/work/2011-01/index.php.htm");
	}
}