package main

import (
	"database/sql"
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"strconv"
	"strings"
	"time"

	_ "github.com/go-sql-driver/mysql"

	"github.com/gorilla/websocket"
)

var Clients = make(map[int]*Client)

type Gdo struct {
	conn *sql.DB
}

func (g *Gdo) Connet() {
	conn, err := sql.Open("mysql", "root:qq282246101@tcp(localhost:3306)/xyz?charset=utf8")
	if err != nil {
		fmt.Println("连接数据库失败")
		conn.Close()
	}
	conn.SetConnMaxLifetime(100)
	conn.SetMaxOpenConns(20)
	g.conn = conn
}

func (g *Gdo) Sql(sql string) *sql.Stmt {
	stmt, err := g.conn.Prepare(sql)
	if err != nil {
		fmt.Printf("prepare sql failed, err:%v\n", err)
		g.conn.Close()
	}
	return stmt

}

func (g *Gdo) StructToMap(obj interface{}) map[string]interface{} {
	t := reflect.TypeOf(obj)
	//获取到里面的值 一维数组 {100 beego gobee}
	v := reflect.ValueOf(obj)
	data := make(map[string]interface{})
	for i := 0; i < t.NumField(); i++ {
		//得到键名                //得到里面的值
		data[t.Field(i).Name] = v.Field(i)
	}
	return data
}

type MyGdo struct {
	Gdo
}

//通过id 找到conn是谁
type Client struct {
	Client *websocket.Conn
	//组名
	GroupUser []string
	//哪个用户
	user string
	fd   int
}
type BroadcastMessageType struct {
	To      string
	Msg     []byte
	MsgType int
}

//用户唯一标识 fd
var fd int

//组成员唯一标识 fd
var GroupFd = make(map[string][]int)

//组成员名 user
var GroupUser = make(map[string][]string)

var ws = websocket.Upgrader{
	WriteBufferSize: 10240,
	ReadBufferSize:  10240,
	//跨域
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func parse_str(uri string) (map[string]string, error) {
	m := make(map[string]string)
	if len(uri) < 1 { // 空字符串
		return m, errors.New("uri is none")
	}
	if uri[0:1] == "?" { // 有没有包含？,有的话忽略。
		uri = uri[1:]
	}

	// 首先分解&（sessionid=22222&token=3333 )变成
	// sessionid=222222 和 token=3333
	pars := strings.Split(uri, "&")
	for _, par := range pars {
		// 然后分解 sessionid=222222 和 token=3333
		parkv := strings.Split(par, "=")
		m[parkv[0]] = parkv[1] // 等号前面是key,后面是value
	}
	return m, nil
}
func main() {
	http.HandleFunc("/wss", func(w http.ResponseWriter, request *http.Request) {
		gdo := MyGdo{}
		gdo.Connet()
		user := ""
		passwd := ""
		room := ""
		if request.URL.Query()["user"] != nil {
			user = request.URL.Query()["user"][0]
			passwd = request.URL.Query()["password"][0]
			room = request.URL.Query()["room"][0]
		} else {
			//单片机接收
			authorization := request.Header["Authorization"][0]
			fmt.Println(authorization)
			arr, err7 := parse_str(authorization)
			if err7 != nil {
				fmt.Print("单片机字符串解析失败")
				return
			}
			fmt.Println(arr)
			user = arr["user"]
			fmt.Println(user)
			passwd = arr["password"]
			room = arr["room"]
		}
		if len(room) < 2 {
			return
		}
		//查询 room对应的id
		row1, _ := gdo.Sql("SELECT id FROM WebsocketRoom WHERE name=?").Query(room)
		var rid int
		defer row1.Close()
		row1.Next()
		row1.Scan(&rid)
		if rid == 0 {
			return
		}
		//验证 帐号密码，房间room
		_, err := gdo.Sql("SELECT id FROM WebsocketUsers WHERE user=? AND passwd=? AND rid=?").Query(user, passwd, rid)
		if err != nil {
			return
		}
		//正确 升级为websocket
		conn, _ := ws.Upgrade(w, request, nil)

		//检查一下 自己组里有没有相同名字的,如果有把之前登录的同名用户下线
		bl := false //如果有同名用户 为 true
		Gstr := []string{}
		if len(GroupFd[room]) > 0 {
			for _, v := range GroupFd[room] {
				if Clients[v].user == user {
					bl = true
					//下线该用户名
					Clients[v].Client.Close()
				}
				if bl == true {
					//重组在线成员列表
					Gint := []int{}
					for _, vv := range GroupFd[room] {
						if v != vv {
							Gint = append(Gint, vv)
							Gstr = append(Gstr, Clients[vv].user)
							//同时向在线该组成员 推送 用户重新上线消息
							Clients[vv].Client.WriteMessage(websocket.TextMessage, []byte("系统消息: "+user+" 重新上线了"))
						}
					}
					//得到去除同名的在线组成员 下一步把自己 加到该分组中
					GroupFd[room] = Gint
					GroupUser[room] = Gstr

					break
				}
			}
			//加到 自己的fd 加到分组中 把自己的用户名 加到 在线组成员列表中
			GroupFd[room] = append(GroupFd[room], fd)
			GroupUser[room] = append(GroupUser[room], user)
		} else {
			GroupFd[room] = []int{fd}
			GroupUser[room] = []string{user}
		}
		// 	//注册到系统 room fd user
		client := Client{conn, GroupUser[room], user, fd}
		Clients[fd] = &client

		fmt.Println(GroupFd[room], GroupUser[room])
		if len(GroupFd[room]) > 1 {
			//推送在线组成员到每个成员中
			for _, v := range GroupFd[room] {
				//不同名 向在该组在线成员推送 上线消息
				if bl == false && v != fd {
					//fmt.Println(Clients[v].user)
					Clients[v].Client.WriteMessage(websocket.TextMessage, []byte("系统消息: "+user+" 上线了"))
				}
				tmp := map[string][]string{}
				tmp["Group"] = GroupUser[room]
				Clients[v].Client.WriteJSON(tmp)
			}
		}

		// 	Gstr = []string{}
		//返回true 向客户表示连接成功
		conn.WriteMessage(websocket.TextMessage, []byte("true"))

		//监听客户端close事件或者非常断开
		conn.SetCloseHandler(func(code int, text string) error {
			Gint := []int{}
			for _, v := range GroupFd[room] {
				if v != client.fd {
					Gint = append(Gint, v)
				}
			}
			//GroupFd[room] = []int{}
			GroupFd[room] = Gint
			//清除Client 连接
			Clients[client.fd].Client.Close()
			Gint = []int{}
			Gstr = []string{}
			//向组里广播该用户已经下线
			for _, v := range GroupFd[room] {
				Clients[v].Client.WriteMessage(websocket.TextMessage, []byte("系统消息: "+client.user+"下线了"))
				if Clients[v].user != client.user {
					Gint = append(Gint, v)
					Gstr = append(Gstr, Clients[v].user)
				}
			}
			GroupFd[room] = Gint
			GroupUser[room] = Gstr
			for _, v := range GroupFd[room] {
				Clients[v].GroupUser = Gstr
				//组里有人下线就更新 所有成员里的 GroupFd
				tmp := map[string][]string{}
				tmp["Group"] = GroupUser[room]
				Clients[v].Client.WriteJSON(tmp)
			}
			fmt.Println("客户端"+strconv.Itoa(client.fd)+"关闭", GroupFd[room])
			return nil
		})
		fd++
		for {
			//第一个参数是数据类型，只是一个int，1代表String，2代表[]byte
			//接收时是普通字符串。服务器返回给客户端是json
			_, data, err := conn.ReadMessage()
			if err != nil {
				fmt.Println(err)
				return
			}
			mm := make(map[string]string)
			date := time.Now().Format("01-02 15:04:05")
			mm["user"] = user
			mm["message"] = string(data)
			mm["date"] = date
			fmt.Println(GroupFd[room], client.fd)
			for _, v := range GroupFd[room] {
				if client.fd != v {
					fmt.Println(v)
					Clients[v].Client.WriteJSON(mm)
				}
			}
		}

	})
	//证书先用其它 放在根目录 tls  即wss
	err := http.ListenAndServeTLS(":1000", "360xyz.cn_bundle.crt", "360xyz.cn.key", nil)
	//下面这个是ws
	//err := http.ListenAndServe(":1000", nil)
	if err != nil {
		fmt.Println("服务器建立失败")
	}

}
