// ************************************
// @package: websocketServer
// @description:
// @author:
// @revision history:
// @create date: 2022-02-16 18:13:45
// ************************************
package websocketServer

import (
	"time"

	"github.com/gorilla/websocket"
	webServer "Framework/webServer"
	logUtil "goutil/logUtil"
)

// 事件回调函数
type EventCallbackFuncs struct {
	// websocket连接事件
	OnConnFunc func(ctx *Context)

	// websocket关闭事件
	OnCloseFunc func(ctx *Context)

	// websocket接收事件
	OnMsgFunc func(ctx *Context, msgType int, msgData []byte)
}

// 用户自定义数据结构
type userDatas struct {
	// WsServer 或 WssServer 指针
	server interface{}

	// 事件回调函数
	eventCallback *EventCallbackFuncs
}

// iServerMgr
// @description: WsServer/WssServer内部管理接口，以便hookHandler中统一访问
type iServerMgr interface {
	// 升级为websocket
	upgrade(ctx *Context) (conn *websocket.Conn, err error)

	// 将连接从连接池删除
	delConn(conn *websocket.Conn)

	// 获取接收到Ping消息时，是否自动回复Pong信息
	GetAutoPong() bool
}

// 回调勾子，将http/https升级为websocket
func hookHandler(webServerCtx *webServer.Context) {
	defer func() {
		if r := recover(); r != nil {
			logUtil.LogUnknownError(r)
		}
	}()

	userDataI := webServerCtx.GetUserData()
	if userDataI == nil {
		return
	}

	userData, ok := userDataI.(*userDatas)
	if !ok {
		logUtil.ErrorLog("userData type error")
		return
	}

	// 通信结束信号
	cls := make(chan struct{})
	ctx := &Context{
		webServerCtx: webServerCtx, // web_server环境
		cls:          cls,          // 关闭连接信号
	}

	var serverMgr iServerMgr
	var conn *websocket.Conn
	var err error

	// 转为iServerMgr
	switch userData.server.(type) {
	case *WsServer:
		if svr, ok := userData.server.(*WsServer); ok {
			serverMgr = svr
		} else {
			logUtil.ErrorLog("server type not WsServer")
			return
		}
	case *WssServer:
		if svr, ok := userData.server.(*WssServer); ok {
			serverMgr = svr
		} else {
			logUtil.ErrorLog("server type not WssServer")
			return
		}
	default:
		logUtil.ErrorLog("server type not WsServer or WssServer")
		return
	}

	// 升级为websocket
	conn, err = serverMgr.upgrade(ctx)
	if err != nil {
		if err.Error() == "connManager(disableNewConn)" {
			// 禁用新连接。正常功能，直接返回
			return
		}

		logUtil.ErrorLog("websocket Upgrade failed: %v", err)
		return
	}

	// 将连接从连接池删除
	defer serverMgr.delConn(conn)

	// 关闭连接
	defer conn.Close()

	// 默认情况下，ReadMessage不会读取到ping/pong/close消息(内部有专门的处理函数)
	// 设置心跳包处理函数
	conn.SetPingHandler(func(msg string) error {
		// 只要收到消息，都需要更新最近一次收到心跳包的时间
		ctx.heartbeat = time.Now()

		if serverMgr.GetAutoPong() {
			// 自动回应一个Pong心跳
			go ctx.SendMessage(websocket.PongMessage, []byte(msg))
		}

		// 接收消息回调
		if userData.eventCallback.OnMsgFunc != nil {
			userData.eventCallback.OnMsgFunc(ctx, websocket.PingMessage, []byte(msg))
		}

		return nil
	})
	// 设置关闭包处理函数
	conn.SetCloseHandler(func(code int, text string) error {
		// 所有向cls写入，都使用select超时结构，以保证这儿不会一直阻塞，确保此协程能退出
		select {
		case <-time.After(time.Millisecond * 10):
		case cls <- struct{}{}:
		}
		return nil
	})

	// 设置最近一次收到心跳包的时间
	ctx.heartbeat = time.Now()

	// 新连接回调
	if userData.eventCallback.OnConnFunc != nil {
		userData.eventCallback.OnConnFunc(ctx)
	}

	// 开启读协程
	go func() {
		defer func() {
			if r := recover(); r != nil {
				logUtil.LogUnknownError(r)

				// 有异常出现(可能是用户回调中出现异常)；执行到这儿，需要关闭连接
				// 所有向cls写入，都使用select超时结构，以保证这儿不会一直阻塞，确保此协程能退出
				select {
				case <-time.After(time.Millisecond * 10):
				case cls <- struct{}{}:
				}
			}
		}()

		for {
			// 注意：ReadMessage不会读取到心跳包和关闭包数据；心跳包/关闭包需要设置专门的处理函数
			// 但内部对心跳包/关闭包的处理，也是由ReadMessage函数触发的(也就是不调用ReadMessage函数，可能也不会触发对心跳包/关闭包的处理)；
			// 经测试和内部代码确认：调用心跳包/关闭包处理函数时，ReadMessage不会返回；心跳包/关闭包处理函数调用完毕后ReadMessage才可能返回
			mt, msg, err := conn.ReadMessage()
			if err != nil {
				// 所有向cls写入，都使用select超时结构，以保证这儿不会一直阻塞，确保此协程能退出
				select {
				case <-time.After(time.Millisecond * 10):
				case cls <- struct{}{}:
				}
				break
			}

			// 只要收到消息，都需要更新最近一次收到心跳包的时间
			ctx.heartbeat = time.Now()

			// 接收消息回调
			if userData.eventCallback.OnMsgFunc != nil {
				userData.eventCallback.OnMsgFunc(ctx, mt, msg)
			}
		}
	}()

	// 等待退出
	<-cls

	// 设置已关闭标志
	ctx.isClosed = true

	// 关闭回调
	if userData.eventCallback.OnCloseFunc != nil {
		userData.eventCallback.OnCloseFunc(ctx)
	}
}
