package modules

import (
	"bytes"
	"context"
	"fmt"
	"net"
	"sync/atomic"
	"time"

	"github.com/buguang01/Logger"
	"github.com/buguang01/bige/messages"
	"github.com/buguang01/util/threads"
)

/*
这是一个Socket的客户端模块
会简单的在连接断开的时候，重新连接
同步发生消息

*/

//这个连接的名字，比如这个连接的目标是什么就叫什么
func SocketCliSetConnName(name string) options {
	return func(mod IModule) {
		mod.(*SocketCliModule).ConnName = name
	}
}

func SocketCliSetPort(ipport string) options {
	return func(mod IModule) {
		mod.(*SocketCliModule).ipPort = ipport
	}
}

//设置路由
func SocketCliSetRoute(route messages.IMessageHandle) options {
	return func(mod IModule) {
		mod.(*SocketCliModule).RouteHandle = route
	}
}

type SocketCliModule struct {
	ConInfo     interface{}             //自定义的连接信息，给上层逻辑使用
	ConnName    string                  //连接名字
	ipPort      string                  //连接服务器的地址
	RouteHandle messages.IMessageHandle //消息路由
	getnum      int64                   //收到的总消息数
	sendnum     int64                   //发出去的消息数
	conn        net.Conn                //连接
	thgo        *threads.ThreadGo       //协程管理器
	isRun       bool                    //是否运行
}

func NewSocketCliModule(opts ...options) *SocketCliModule {
	result := &SocketCliModule{
		ConnName:    "SocketName",
		ipPort:      ":8082",
		RouteHandle: messages.JsonMessageHandleNew(),
		getnum:      0,
		sendnum:     0,
		thgo:        threads.NewThreadGo(),
		isRun:       false,
	}

	for _, opt := range opts {
		opt(result)
	}
	return result
}

//Init 初始化
func (mod *SocketCliModule) Init() {
	var err error
	mod.conn, err = net.Dial("tcp", mod.ipPort)
	if err != nil {
		panic(err)
	}
}

//Start 启动
func (mod *SocketCliModule) Start() {
	mod.isRun = true
	mod.thgo.Go(mod.handle)
	Logger.PStatus("Socket Cli Module Start.")
}

//Stop 停止
func (mod *SocketCliModule) Stop() {
	mod.isRun = false
	mod.conn.Close()
	mod.thgo.CloseWait()
	Logger.PStatus("Socket Cli Module Stop.")
}

//PrintStatus 打印状态
func (mod *SocketCliModule) PrintStatus() string {
	return fmt.Sprintf(
		"\r\n\t\tsocket cli Module\t:%d/%d\t(get/send)",
		atomic.LoadInt64(&mod.getnum),
		atomic.LoadInt64(&mod.sendnum))
}

func (mod *SocketCliModule) handle(ctx context.Context) {
	buf := &bytes.Buffer{}
listen:
	for {
		rdbuff := make([]byte, 10240)
		n, err := mod.conn.Read(rdbuff)
		if err != nil || n == 0 {
			if err != nil {
				Logger.PDebug("Socket Cli Conn Read Error:%+v.", err)
			} else {
				Logger.PDebug("Socket Cli Conn Read Error EOF.")

			}
			if mod.reConn() {
				continue
			} else {
				return
			}
		}

		buf.Write(rdbuff[:n])
		buff := buf.Bytes()
		if msglen, ok := mod.RouteHandle.CheckMaxLenVaild(buff); ok {
			buff = buf.Next(int(msglen))
		} else {
			if msglen == 0 {
				//消息长度异常
				break listen
			}
			continue
		}

		msg, err := mod.RouteHandle.Unmarshal(buff)

		if err != nil {
			Logger.PInfo("socket cli RouteHandle Unmarshal Error:%s", err.Error())
			return
		}
		modmsg, ok := msg.(messages.ISocketMessageHandle)
		if !ok {
			Logger.PInfo("Not is socket cli Msg:%+v", msg)
			return
		} else {
			Logger.PInfo("socket cli Get Msg:%+v", msg)
		}

		atomic.AddInt64(&mod.getnum, 1)
		mod.thgo.Try(func(ctx context.Context) {
			//因为是主动连接，所以不会返回连接，消息里本身应该会带来源信息
			modmsg.SocketDirectCall(nil)
		}, nil, nil)
	}
}

//如果连接断开重连
func (mod *SocketCliModule) reConn() (result bool) {
	result = false
	if mod.isRun {
		for !result {
			mod.thgo.Try(func(ctx context.Context) {
				mod.conn.Close()
				mod.Init()
				result = true
			}, func(err interface{}) {
				time.Sleep(time.Second)
			}, nil)
		}
	}
	return result
}

/*
下面这二个方法主要还是例子，
是给game连接gate的时候使用，
如果有异步啊，队列管理啊等的需求，可以自己写对应的全局方法来实现会比较好

*/

//同步写入消息
func (mod *SocketCliModule) AddMsgSyn(msg messages.IMessage) error {
	if buff, err := mod.RouteHandle.Marshal(msg.GetAction(), msg); err != nil {
		return err
	} else if _, err = mod.conn.Write(buff); err != nil {
		return err
	}
	atomic.AddInt64(&mod.sendnum, 1)
	return nil
}

//同步写入网关的消息
func (mod *SocketCliModule) AddMsgSynByGate(gate messages.IGateMessage, msg interface{}) error {
	if buff, err := mod.RouteHandle.GateMarshal(gate, msg); err != nil {
		return err
	} else if _, err = mod.conn.Write(buff); err != nil {
		return err
	}
	atomic.AddInt64(&mod.sendnum, 1)
	return nil
}
