package game

import (
	"encoding/json"
	"net/http"
	"reflect"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

//数据包-用于前后端的RPC调用
type dataPack struct {
	Type int32         `json:"type"`
	Func string        `json:"func"`
	Args []interface{} `json:"args"`
}

//CheckOrigin防止跨站点的请求伪造
var upGrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		//直接通过，因为目前还没必要做检测
		return true
	},
}

//Socket处理器
func socketHandler(c *gin.Context) {
	ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		return
	}
	se := createEntity(ws)
	defer invokeFunc(se, "OnDestroy")
	defer ws.Close()
	invokeFunc(se, "OnLoad")
	for {
		_, message, err := ws.ReadMessage()
		if err != nil {
			break
		}
		dp := dataPack{}
		json.Unmarshal(message, &dp)
		invokeFuncByDataPack(se, &dp)
	}
}

//定义函数-创建Socket实体
type CreateEntity func(conn *websocket.Conn) interface{}

//自定义函数-创建Socket实体
var createEntity CreateEntity

//开启服务监听
func StartServer(port int, path string, createEntityFunc CreateEntity) {
	createEntity = createEntityFunc
	r := gin.Default()
	r.GET(path, socketHandler)
	r.Run(":" + strconv.Itoa(port))
}

//向客户端发送数据包
func SendPackFunction(ws *websocket.Conn, func_ string, args ...interface{}) {
	dp := dataPack{Func: func_, Args: args, Type: 1}
	ws.WriteJSON(&dp)
}

//通过反射调用函数
func invokeFunc(obj interface{}, func_ string, args ...interface{}) {
	ctx := reflect.ValueOf(obj)
	if strings.Contains(func_, ".") {
		func_Split := strings.Split(func_, ".")
		ctx = ctx.FieldByName(func_Split[0])
		func_ = func_Split[1]
	}
	method := ctx.MethodByName(func_)
	if !method.IsValid() {
		return
	}
	argsLen := len(args)
	argsList := make([]reflect.Value, argsLen)
	for i := 0; i < argsLen; i++ {
		argsList[i] = reflect.ValueOf(args[i])
	}
	method.Call(argsList)
}

//检测开关-在远程调用前是否进行合法检测
var CheckServiceFuncMapBeforInvoke = false

//为了防止非法远程调用，只允许调用Map中记录的方法
var ServiceFuncMap = make(map[string]bool)

//根据客户端传来的RPC数据包调用指定的函数
func invokeFuncByDataPack(obj interface{}, dp *dataPack) {
	if dp.Type == 1 {
		if !CheckServiceFuncMapBeforInvoke || ServiceFuncMap[dp.Func] {
			invokeFunc(obj, dp.Func, dp.Args...)
		}
	}
}
