
package org.tio.sitexxx.im.server;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.core.stat.ChannelStat;
import org.tio.monitor.RateLimiterWrap;
import org.tio.sitexxx.im.common.CommandHandler;
import org.tio.sitexxx.im.common.CommandStat;
import org.tio.sitexxx.im.common.ImPacket;
import org.tio.sitexxx.im.common.ImSessionContext;
import org.tio.sitexxx.im.common.utils.ImUtils;
import org.tio.sitexxx.im.server.handler.ImServerHandler;
import org.tio.sitexxx.service.Command;
import org.tio.utils.hutool.ClassScanAnnotationHandler;
import org.tio.utils.hutool.ClassUtil;
import org.tio.utils.jfinal.P;
import org.tio.utils.json.Json;

import java.util.Map;
import java.util.TreeMap;

/**
 * @author tanyaowu 
 * 2016年9月7日 下午5:39:49
 */
public class PacketDispatcher {
	private static Logger log = LoggerFactory.getLogger(PacketDispatcher.class);

	/**
	 * 路径和对象映射
	 * key: Command
	 * value: object
	 */
	public Map<Command, ImServerHandler> handlerMap = new TreeMap<>();

	/**
	 * 路径和class映射
	 * 只是用来打印的
	 * key: Command
	 * value: Class
	 */
	public Map<Command, Class<?>> pathClassMap = new TreeMap<>();

	/**
	 * 分发消息到处理者
	 * @param imPacket
	 * @param channelContext
	 * @param isWebsocket
	 * @throws Exception
	 * @author: tanyaowu
	 */
	public void dispatch(ImPacket imPacket, ChannelContext channelContext, boolean isWebsocket) throws Exception {
		Command command = imPacket.getCommand();
		ImSessionContext imSessionContext = ImUtils.getImSessionContext(channelContext);

		log.debug(channelContext+", 收到命令:"+command);

		//检查握手情况
		if (!imSessionContext.isHandshaked()) {
			if (command != Command.WxHandshakeReq) {
				log.warn("{} 第一个业务包必须为握手包，本次命令:{}", channelContext.toString(), command);
				Tio.remove(channelContext, "第一个业务包必须为握手包");
				return;
			}
		}

		//检查消息发送频率
		ChannelStat channelStat = channelContext.stat;
		if (channelStat.receivedPackets.get() > P.getInt("skip.warn.count")) { //前面几条命令不计入令牌桶
			RateLimiterWrap rateLimiterWrap;

			rateLimiterWrap = imSessionContext.getRequestRateLimiter();

			boolean[] limitTest = rateLimiterWrap.tryAcquire();
			if (!limitTest[0] && !limitTest[1]) {
				String remark = channelContext + " 访问太频繁， 将拉黑其IP，本次命令:" + command;
				log.warn(remark);
				return;
			} else if (!limitTest[0] && limitTest[1]) {
				log.warn("{} 访问太频繁，将警告一次，本次命令:{}", channelContext, command);
				return;
			}
		}

		ImServerHandler handler = handlerMap.get(command);
		if (handler != null) {
			log.debug("{} 收到消息:{}, isWebsocket:{}", channelContext, command, isWebsocket);
			handler.handler(imPacket, channelContext, isWebsocket);
		} else {
			log.warn("命令码[{}]没有对应的处理类", command);
		}
		CommandStat.getCommandStat(command).handled.incrementAndGet();
	}

	/**
	 * 
	 * @author: tanyaowu
	 */
	public PacketDispatcher(String[] scanPackages) {
		if (scanPackages != null) {
			for (String pkg : scanPackages) {
				try {
					ClassUtil.scanPackage(pkg, new ClassScanAnnotationHandler(CommandHandler.class) {
						@Override
						public void handlerAnnotation(Class<?> clazz) {
							try {
								Object bean = clazz.getConstructor().newInstance();

								if (!(bean instanceof ImServerHandler)) {
									log.error("{}没有实现 {}", clazz.getName(), ImServerHandler.class.getName());
									return;
								}

								ImServerHandler imServerHandler = (ImServerHandler) bean;

								CommandHandler mapping = clazz.getAnnotation(CommandHandler.class);
								Command command = mapping.value();

								Object obj = handlerMap.get(command);
								if (obj != null) {
									log.error("mapping[{}] already exists in class [{}]", command, obj.getClass().getName());
								} else {
									handlerMap.put(command, imServerHandler);
									pathClassMap.put(command, clazz);
								}

							} catch (Exception e) {
								//log.error("", e);
							}

						}
					});
				} catch (Exception e) {
					log.error("", e);
				}
			}
			log.warn("command mapping\r\n{}", Json.toFormatedJson(pathClassMap));
		}
	}
}
