package com.ruoyi.web.controller.common;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;

import cn.hutool.core.util.StrUtil;

@Component
@ServerEndpoint("/websocket/{userId}")
public class WebSocketController {

    private static Logger log = LoggerFactory.getLogger(WebSocketController.class);
    /**
     * 静态变量，用来记录当前在线连接数，线程安全的类。
     */
    private static AtomicInteger sessionCount = new AtomicInteger(0);

    /**
     * 存放所有在线的客户端
     */
    private static Map<String, Session> sessionMap = new ConcurrentHashMap<>();
    /**
     * key Session.id value userId
     */
    private static Map<String, String> sidMap = new ConcurrentHashMap<>();

    /**
     *** 连接建立成功调用的方法。由前端<code>new WebSocket</code>触发**
     * 
     * @param sid 每次页面建立连接时传入到服务端的id，比如用户id等。可以自定义。*
     * @param session 与某个客户端的连接会话，需要通过它来给客户端发送消息
     */

    @OnOpen
    public void onOpen(@PathParam("userId") String userId, Session session) {
        log.debug("连接建立中 ==> session_id = {}， userId = {}", session.getId(), userId);
        sessionMap.put(userId, session);
        sidMap.put(session.getId(), userId);
        // 在线数加1
        sessionCount.incrementAndGet();
        sendToOne(userId, "连接成功");
        log.debug("连接建立成功，当前在线数为：{} ==> 开始监听新连接：session_id = {} , userId = {} ", sessionCount, session.getId(), userId);
    }

    /**
     * 连接关闭调用的方法。由前端<code>socket.close()</code>触发
     *
     * @param sid
     * @param session
     */
    @OnClose
    public void onClose(@PathParam("userId") String userId, Session session) {
        // 从 Map中移除
        sessionMap.remove(userId);
        sidMap.remove(session.getId());
        // 在线数减1
        sessionCount.decrementAndGet();
        log.debug("连接关闭成功，当前在线数为：{} ==> 关闭该连接信息：session_id = {} , userId = {}", sessionCount, session.getId(), userId);
    }

    /**
     * 收到客户端消息后调用的方法。由前端<code>socket.send</code>触发 * 当服务端执行toSession.getAsyncRemote().sendText(xxx)后，前端的socket.onmessage得到监听。
     *
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        try {
            JSONObject jsonObject = JSON.parseObject(message);
            String toId = jsonObject.getString("toId");
            String msg = jsonObject.getString("message");
            String fromId = sidMap.get(session.getId());
            log.debug("服务端收到客户端消息 ==> fromId= {}, toId = {}, message = {}", fromId, toId, message);
            /**
             * 模拟约定：如果未指定sid信息，则群发，否则就单独发送
             */
            if (StrUtil.isBlank(toId)) {
                sendToAll(fromId, msg);
            } else {
                sendToOne(toId, msg);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 发生错误调用的方法
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("WebSocket发生错误，错误信息为：{}", error.getMessage(), error);
    }

    /**
     * 群发消息
     *
     * @param message 消息
     */
    private void sendToAll(String fromId, String message) {
        // 遍历在线map集合
        sessionMap.forEach((toId, toSession) -> {
            // 排除掉自己
            if (StrUtil.equalsIgnoreCase(fromId, toId)) {
                log.debug("服务端给客户端群发消息 ==> form = {}, to = {}, message = {}", fromId, toId, message);
                toSession.getAsyncRemote().sendText(message);
            }
        });
    }

    /**
     * 指定发送消息
     *
     * @param toSid
     * @param message
     */
    public void sendToOne(String toId, String message) {
        // 通过sid查询map中是否存在
        Session toSession = sessionMap.get(toId);
        if (toSession == null) {
            log.info("服务端给客户端发送消息 ==> toId = {} 不存在, message = {}", toId, message);
            return;
        }
        // 异步发送
        log.debug("服务端给客户端发送消息 ==> toAppId = {}, message = {}", toId, message);
        toSession.getAsyncRemote().sendText(message);
        /*
        // 同步发送
        try {
           toSession.getBasicRemote().sendText(message);
        } catch (IOException e) {
           log.error("发送消息失败，WebSocket IO异常");
           e.printStackTrace();
        }*/
    }
}