
package fr.zng.xxzx.netty.wss;

import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static io.netty.handler.codec.http.HttpHeaders.setContentLength;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import fr.zng.xxzx.biz.WebSessionDto;
import fr.zng.xxzx.biz.WebUsDto;
import fr.zng.xxzx.common.cacheData.ZngCacheData;
import fr.zng.xxzx.common.dao.DvcLogLockDao;
import fr.zng.xxzx.common.dao.EquipDao;
import fr.zng.xxzx.common.dao.StoreDao;
import fr.zng.xxzx.common.dao.UserDao;
import fr.zng.xxzx.common.dao.impl.DvcLogLockDaoImpl;
import fr.zng.xxzx.common.dao.impl.EquipDaoImpl;
import fr.zng.xxzx.common.dao.impl.StoreDaoImpl;
import fr.zng.xxzx.common.dao.impl.UserDaoImpl;
import fr.zng.xxzx.common.entity.DvcLogLockEntity;
import fr.zng.xxzx.common.entity.EquipEntity;
import fr.zng.xxzx.common.entity.StoreEntity;
import fr.zng.xxzx.common.entity.UserEntity;
import fr.zng.xxzx.common.util.CommUtil;
import fr.zng.xxzx.common.util.DesUtil;
import fr.zng.xxzx.netty.dispatcher.InboundDispatcher;
import fr.zng.xxzx.util.StringUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.UUID;

import javax.swing.JTextArea;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;

import org.apache.log4j.Logger;

import com.alibaba.fastjson.JSONObject;


public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>
{
    private static final Logger logger = Logger .getLogger(WebSocketServerHandler.class.getName());

    private WebSocketServerHandshaker handshaker;

    private JTextArea jta;
    //客户端ip地址
//      private String ip = "";
      

      public WebSocketServerHandler(JTextArea jta){
          this.jta = jta;
      }
   

    static final ChannelGroup channels = new DefaultChannelGroup( GlobalEventExecutor.INSTANCE );

    @Override
    public void channelActive(final ChannelHandlerContext ctx) throws Exception 
    {
    	System.out.println("channelActive : ");
    	// Once session is secured, send a greeting and register the channel to the global channel
    	// list so the channel received the messages from others.
    	ctx.pipeline().get(SslHandler.class).handshakeFuture()
			.addListener(new GenericFutureListener<Future<Channel>>() {
		    @Override
		    public void operationComplete(Future<Channel> future)    throws Exception 
		    {
//		    	ctx.writeAndFlush("Welcome to "	+ InetAddress.getLocalHost().getHostName()	+ " secure chat service!\n");
//		    	ctx.writeAndFlush("Your session is protected by " + ctx.pipeline().get(SslHandler.class).engine() .getSession().getCipherSuite() + " cipher suite.\n");
		    	InetSocketAddress saddr = (InetSocketAddress) ctx.channel().remoteAddress();
				String ip = saddr.toString();
		    	channels.add(ctx.channel());
				// 将通道按用户ip地址存入map
				if (!ZngCacheData.sessionWebMap.containsKey(ip)) {
					WebSessionDto sd = new WebSessionDto();
					sd.setChannel(ctx.channel());
					ZngCacheData.sessionWebMap.put(ip, sd);
				}
				CommUtil.doPrint(jta, "Key", "web建立连接" + ctx.channel().remoteAddress().toString());

		    }
		});
    }

    
    @Override
    public void messageReceived(ChannelHandlerContext ctx, Object msg)  throws Exception 
    {
    	System.out.println("WebSocket messageReceived : "+msg);
		// 传统的HTTP接入
		if (msg instanceof FullHttpRequest) {
		    handleHttpRequest(ctx, (FullHttpRequest) msg);
		}
		// WebSocket接入
		else if (msg instanceof WebSocketFrame) {
		    handleWebSocketFrame(ctx, (WebSocketFrame) msg);
		}
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    	ctx.flush();
    }

    private void handleHttpRequest(ChannelHandlerContext ctx,   FullHttpRequest req) throws Exception
    {
    	System.out.println("handleHttpRequest : ");
		// 如果HTTP解码失败，返回HHTP异常
		if (!req.getDecoderResult().isSuccess()	|| (!"websocket".equals(req.headers().get("Upgrade")))) 
		{
		    sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1,  BAD_REQUEST));
		    return;
		}
	
		// 构造握手响应返回，本机测试
		WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("wss://localhost:9999/websocket", null, false);
		handshaker = wsFactory.newHandshaker(req);
		if (handshaker == null) {
		    WebSocketServerHandshakerFactory .sendUnsupportedWebSocketVersionResponse(ctx.channel());
		} else {
		    handshaker.handshake(ctx.channel(), req);
		}
    }
    
	/**
	 * 当客户端断开连接
	 * */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		InetSocketAddress saddr = (InetSocketAddress) ctx.channel().remoteAddress();
		String ip = saddr.toString();
		// 通道关闭，移除map中ip
		doFize(ip);
		
		try {
			finalize();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		CommUtil.doPrint(jta, "Key", "web关闭连接" + ctx.channel().remoteAddress().toString());
	}
	
	public String doFize(String ip) {
		String code = "";
		ZngCacheData.sessionWebMap.remove(ip);
		// 移除map中的ip
		if (ZngCacheData.sessionWebIPCodeDevice.containsKey(ip)) {
				code = ZngCacheData.sessionWebIPCodeDevice.get(ip);
				ZngCacheData.sessionWebCodeUsed.remove(code);
				ZngCacheData.sessionWebCodeIPDevice.remove(code);
				ZngCacheData.sessionWebIPCodeDevice.remove(ip);
				
		}
		return code;
	}

    private void handleWebSocketFrame(ChannelHandlerContext ctx,   WebSocketFrame frame)
    {
    	
		// 判断是否是关闭链路的指令
		if (frame instanceof CloseWebSocketFrame) {
		    handshaker.close(ctx.channel(),  (CloseWebSocketFrame) frame.retain());
		    return;
		}
		// 判断是否是Ping消息
		if (frame instanceof PingWebSocketFrame) {
		    ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
		    return;
		}
		// 本例程仅支持文本消息，不支持二进制消息
		if (!(frame instanceof TextWebSocketFrame))
		{
		    throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass().getName()));
		}
	
		// 返回应答消息
		String request = ((TextWebSocketFrame) frame).text();
		// 哪个站发过来的发给哪个中心
		logger.info("接收到客户数据：" + request);
		CommUtil.doPrint(jta, "Key", "接收到客户数据:" + request);
        handleWebSocket(ctx,request);
//		ctx.channel().write(new TextWebSocketFrame(request	+ " , WebSocket service Now at:" + new java.util.Date().toString()));
		System.out.println("handleWebSocketFrame : "+request);
    }
    
    @OnOpen
	public void onOpen(@PathParam("imei") String imei, @PathParam("openid") String openid, Session session) {
		CommUtil.doPrint(jta, "带参数的websocket请求: ", " imei=" + imei + " openid=" + openid + " session=" + session);
	}
    
    /**
     * webSocket数据分析
     * @param ctx
     * @param sendText
     */
    @SuppressWarnings("unused")
	private void handleWebSocket(ChannelHandlerContext ctx, String sendText) {
    	InetSocketAddress saddr = (InetSocketAddress) ctx.channel().remoteAddress();
		String ip = saddr.toString();
		try {
			// 消息回复
			String response = "";
			JSONObject jsStr = JSONObject.parseObject(sendText);
			// 消息类型
			String msgType = jsStr.get("msg_type").toString();
			// 心跳
			if ("heart".equals(msgType)) {
				response = "{\"ret\":1,\"msg\":\"heart\"}";
			} else if ("01".equals(msgType)) {
				// 未加密设备号
				String equipmentCode = "";
				// 扫码验证 01
				String message = jsStr.get("msg").toString();
				String[] a = message.split("/");
				String imei = a[0];
				logger.info("imei：" + imei);
				String openid = a[1];
				logger.info("openid：" + openid);
				CommUtil.doPrint(jta, "imei", "加密设备号" + imei);
				// dvcloglock表日志
				StringBuffer log = new StringBuffer();
				// 检验设备号是否存在
				EquipEntity en = null;
				EquipDao dao = new EquipDaoImpl();
				en = dao.getDetailByPk(imei.toUpperCase());
				if (en.getId() == null) {
					// 设备号不存在
					response = "{\"status\":\"21\",\"msg\":\"设备号不存在\"}";
					CommUtil.doPrint(jta, "21", " 设备号不存在");
					log.append("设备号不存在");
				} else {
					// 设备号imei
					equipmentCode = en.getImei();
					log.append("设备号" + equipmentCode);
					// 设备是否在线
					String aip = ZngCacheData.sessionCodeIP.get(equipmentCode);
					if (StringUtil.isEmpty(aip) || "null".equals(aip)) {
						// 不在线
						response = "{\"status\":\"23\",\"msg\":\"设备离线\"}";
						CommUtil.doPrint(jta, "23", " 设备离线");
						log.append(" 设备离线");
					} else {
						// 在线
						// 判断该设备是否已经被扫描
						// openid 查询用户表 获取用户编号、用户名称
						// 查询用户信息
						UserEntity userEn = new UserEntity();
						UserDao userDao = new UserDaoImpl();
						userEn = userDao.getDetailByPk(openid);
						log.append(" 用户ID" + openid);
						if (userEn.getId() == null) {
							// 用户名不存在
							response = "{\"status\":\"22\",\"msg\":\"非法用户\"}";
							CommUtil.doPrint(jta, "22", " 非法用户");
							log.append(" 非法用户");
						} else {
							// 用户名存在 验证是否通过验证
							if ("0".equals(userEn.getSts())) {
								// 锁住状态，防止重复扫码
								if (ZngCacheData.sessionWebCodeUsed.containsKey(equipmentCode)) {
									response = "{\"status\":\"20\",\"msg\":\"设备正在使用,请稍后再试\",\"equipno\":\""
											+ equipmentCode + "\",\"addr\":\"" + en.getAddress() + "\"}";
									log.append(" 设备正在使用,请稍后再试");
									// 通信段回复
									ctx.channel().writeAndFlush(new TextWebSocketFrame(response));
									return;
								}
								WebUsDto usdto = new WebUsDto();
								usdto.setCode(equipmentCode);
								usdto.setTimestamp(System.currentTimeMillis());
								usdto.setSts(1);
								ZngCacheData.sessionWebCodeUsed.put(equipmentCode, usdto);
								// 通过验证
								// 设备关联门店表 获取门店名称、门店编号、门店组、门店地址、门店联系人电话
								String sno = en.getSno();
								StoreDao storedao = new StoreDaoImpl();
								StoreEntity storeen = new StoreEntity();
								storeen = storedao.getDetailByPk(sno);
								if (storeen.getId() != null) {
									// 门店名称
									String storeName = storeen.getSname();
									// 门店编号
									String storeNo = storeen.getSno();
									// 门店组
									String storeGroup = storeen.getGrno();
									// 门店地址
									String storeAddr = storeen.getAddr();
									// 门店联系人电话
									String storeTel = storeen.getTel();
									log.append(" 门店编号:" + storeNo);
									log.append(" 门店名称:" + storeName);
									log.append(" 门店组:" + storeGroup);
									log.append(" 门店地址:" + storeAddr);
									log.append(" 门店联系人电话:" + storeTel);
									
									// 用户编号
									String uno = "";
									// 用户名称
									String uname = "";
									// 设备版本号
									String ver = "";
									ver = ZngCacheData.sessionCodeVersion.get(equipmentCode);
									log.append(" 版本号:" + ver);
									
									uno = userEn.getUno();
									uname = userEn.getName();
									// 检测设备状态
									String deviceStatus = ZngCacheData.sessionDeviceStatus.get(equipmentCode);
									if ("01".equals(deviceStatus)) {
										// 空闲
										response = "{\"status\":\"01\",\"msg\":\"设备空闲\",\"sname\":\"" + storeName
												+ "\",\"sno\":\"" + storeNo + "\",\"srp\":\"" + storeGroup
												+ "\",\"addr\":\"" + storeAddr + "\",\"equipno\":\"" + equipmentCode
												+ "\",\"tel\":\"" + storeTel + "\",\"uno\":\"" + uno + "\",\"uname\":\""
												+ uname + "\",\"ver\":\"" + ver + "\"}";
										CommUtil.doPrint(jta, "01", " 设备空闲");

									} else if ("02".equals(deviceStatus)) {
										// 开门中
										response = "{\"status\":\"20\",\"msg\":\"设备使用中\",\"sname\":\"" + storeName
												+ "\",\"sno\":\"" + storeNo + "\",\"srp\":\"" + storeGroup
												+ "\",\"addr\":\"" + en.getAddress() + "\",\"equipno\":\"" + equipmentCode
												+ "\",\"tel\":\"" + storeTel + "\",\"uno\":\"" + uno + "\",\"uname\":\""
												+ uname + "\",\"ver\":\"" + ver + "\"}";
										CommUtil.doPrint(jta, "20", " 设备使用中");
									}
									log.append(" 设备状态:" + deviceStatus);
								} else {
									CommUtil.doPrint(jta, "22", " 门店信息不存在");
								}
							} else {
								// 未通过验证
								response = "{\"status\":\"22\",\"msg\":\"非法用户\"}";
								CommUtil.doPrint(jta, "22", " 非法用户");
								log.append(" 用户未通过验证");
							}
						}
					}
				}
				// 插入日志
				insertDvcLogLock(equipmentCode, log.toString(), "1", "扫一扫获取设备状态");
				CommUtil.doPrint(jta, "插入dvcloglock表日志", log.toString());
			} else if ("02".equals(msgType)) {
				// 开锁请求 02
				String message = jsStr.get("msg").toString();
				logger.info("02 接收到客户数据：" + message);
				String[] a = message.split("/");
				// 未加密的设备号
				String equipment= "";
				// 插入dvcloglock表log
				StringBuffer log = new StringBuffer();
				if (a.length >= 7) {
					// 版本号
					String version = a[0];
					// 用户id uno
					String userId = a[1];
					// 类别
					String type = a[2];
					// 设备号
					String device = a[3];
					// 时间戳
					String time = a[4];
					// 经度
					String jingdu = a[5];
					// 纬度
					String weidu = a[6];
					// 验证长度 
					if (checkLength(version, 2)) {
						CommUtil.doPrint(jta, "Key", "版本号:" + version);
					}
					if (checkLength(userId, 8)) {
						CommUtil.doPrint(jta, "Key", "用户id:" + userId);
					}
					if (checkLength(type, 2)) {
						CommUtil.doPrint(jta, "Key", "类别:" + type);
					}
					// if (checkLength(device, 32)) {
					CommUtil.doPrint(jta, "Key", "设备号:" + device);
					// }
					if (checkLength(time, 10)) {
						CommUtil.doPrint(jta, "Key", "时间戳:" + time);
					}
					// if (checkLength(jingdu, 12)) {
					CommUtil.doPrint(jta, "Key", "经度:" + jingdu);
					// }
					// if (checkLength(weidu, 12)) {
					CommUtil.doPrint(jta, "Key", "纬度:" + weidu);
					// }

					// 通过设备号找到该设备号是否在线
					EquipEntity en = new EquipEntity();
					EquipDao dao = new EquipDaoImpl();
					en = dao.getDetailByPk(device);
					if (en.getId() != null) {
						equipment = en.getImei();
						log.append("版本号:" + version + "用户ID:" + userId + "类型:" + type + "设备号：" + equipment + "时间戳:" + time + "经度:" + jingdu + "纬度:" + weidu);
						CommUtil.doPrint(jta, "Key", "equipment:" + equipment);
						// 存入map 设备号 ip
						if (!ZngCacheData.sessionWebCodeIPDevice.containsKey(equipment)) {
							ZngCacheData.sessionWebCodeIPDevice.put(equipment, ip);
							ZngCacheData.sessionWebIPCodeDevice.put(ip, equipment);
						}
						// 判断经度纬度
						String jingduEn = en.getJd();
						String weiduEn = en.getWd();

						// 测试 TODO
						jingduEn = jingdu;
						weiduEn = weidu;

						if (!jingduEn.equals(jingdu) || !weiduEn.equals(weidu)) {
							// msg = "开锁位置不正确(请到位置边进行)";
							response = "{\"status\":\"33\",\"msg\":\"开锁位置不正确(请到位置边进行)\"}";
							CommUtil.doPrint(jta, "33", " 开锁位置不正确(请到位置边进行)");
							log.append(" 开锁位置不正确");
						} else {
							// 经纬度一致 发送请求 哪个通道？
							// 获取当前时间的时间戳
							long currentTime = System.currentTimeMillis() / 1000;
							// 时间戳加密
							String enTime = DesUtil.encryptDES(currentTime + "000000", InboundDispatcher.PASSWORD);
							// 开门命令
							String orderData = InboundDispatcher.ORDER_2001;
							// 开门data type 01 存 02 清 03 结账
							String sendData = version + equipment + currentTime + enTime + type + userId;
							// 获取该设备号对应的netty连接的ip
							String nettyIp = ZngCacheData.sessionCodeIP.get(equipment);
							if (StringUtil.isEmpty(nettyIp) || "null".equals(nettyIp)) {
								// 请求失败 设备离线
								// status = "32";
								// msg = "开锁请求失败,设备离线";
								response = "{\"status\":\"32\",\"msg\":\"开锁请求失败,设备离线\"}";
								CommUtil.doPrint(jta, "32", " 开锁请求失败,设备离线");
								log.append(" 开锁请求失败,设备离线");
							} else {
								// 获取该netty连接的ip所对应的通道
								// 申请开门
								doOpen(equipment, type,userId);
								response = "{\"status\":\"31\",\"msg\":\"开锁请求成功\"}";
								CommUtil.doPrint(jta, "31", " 开锁请求成功");
								log.append(" 开锁请求成功");
							}
						}
					} else {
						// 无此设备号
						// status = "32";
						// msg = "开锁请求失败,无此设备号";
						response = "{\"status\":\"32\",\"msg\":\"开锁请求失败,无此设备号\"}";
						CommUtil.doPrint(jta, "32", " 开锁请求失败,无此设备号");
						log.append(" 开锁请求失败,无此设备号");
					}
				}
				// 插入日志
				insertDvcLogLock(equipment, log.toString(), "1", "小程序端请求开锁");
			}
//			if ("heart".equals(msgType)) {
				// 通信段回复
				ctx.channel().writeAndFlush(new TextWebSocketFrame(response));
//			}

		} catch (Exception e) {
			logger.info(" 数据格式不正确.", e);
			CommUtil.doPrint(jta, "Error", " 数据格式不正确:" + e.getMessage());
		}
	}
    
    private void doOpen(String imei, String loctyp,String userId) {
		// 获取当前时间的时间戳
		long time = System.currentTimeMillis() / 1000;
		// 时间戳加密
		String enTime = DesUtil.encryptDES(time + "000000", InboundDispatcher.PASSWORD);

		EquipDao dao = new EquipDaoImpl();
		EquipEntity en = new EquipEntity();
		en.setImei(imei);
		List<EquipEntity> list = dao.searchByno(en);

		// 用户ID 00000010
		String replyData = CommUtil.doMakeData("2001", list.get(0).getVer(), imei, String.valueOf(time), enTime,
				loctyp + userId);
		// 回复
		Channel ch = ZngCacheData.sessionMap.get(ZngCacheData.sessionCodeIP.get(imei)).getChannel();
		CommUtil.doPrint(jta, "", " 获取发送数据的channel ");
		ch.writeAndFlush(replyData);
		ch.flush();
		CommUtil.doPrint(jta, "", " 发送数据成功 .." + replyData);
	}
    
    private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) 
    {
		// 返回应答给客户端
		if (res.getStatus().code() != 200)
		{
		    ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(),  CharsetUtil.UTF_8);
		    res.content().writeBytes(buf);
		    buf.release();
		    setContentLength(res, res.content().readableBytes());
		}
	
		// 如果是非Keep-Alive，关闭连接
		ChannelFuture f = ctx.channel().writeAndFlush(res);
		if (!isKeepAlive(req) || res.getStatus().code() != 200) 
		{
		    f.addListener(ChannelFutureListener.CLOSE);
		}
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)  throws Exception {
    	System.out.println("exceptionCaught : ");
    	cause.printStackTrace();
    	InetSocketAddress saddr = (InetSocketAddress) ctx.channel().remoteAddress();
		String ip = saddr.toString();
    	doFize(ip);
		
		try {
			finalize();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		logger.info("web异常.", cause);
    	CommUtil.doPrint(jta, "Key", "web异常" + ctx.channel().remoteAddress().toString() + cause.getMessage());
    	ctx.close();
    	
    }
    
    /**
	 * 长度检验
	 * 
	 * @return
	 */
	private Boolean checkLength(String data, int length) {
		if (data.length() == length) {
			return true;
		} else {
			return false;
		}
	}
    
    /**
	 * 插入通信日志表
	 */
	private void insertDvcLogLock(String imei,String log,String type,String action){
		DvcLogLockEntity en = new DvcLogLockEntity();
		DvcLogLockDao dao = new DvcLogLockDaoImpl();
		// 操作
		en.setAction(action);
		// 设备号
		en.setImei(imei);
		// 日志内容
		en.setLog(log);
		// 1:ws 2: sock
		en.setType(type);
		// uuid
		en.setUuid(UUID.randomUUID().toString().replaceAll("-", ""));
		// 时间
		en.setCtime(System.currentTimeMillis()/1000 + "");
		dao.insert(en);
	}
}
