/*
遇到mod的问题，先ctrl+s保存一下，然后直接go run main.go  如果不行的话，再使用go mod init，之后再go run main.go
*/
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/gomodule/redigo/redis"
)

var channelSize int
var Status map[string]int    //store each users' status
var status_learning []string //users who are in learning phase

//all users and theri ip address
var NameList []string
var IpList map[string]string

//test2 uses this hashmap to get from Sample Data, in each round, there are how many messages
var wantToSendHash map[string]int

var packetLength int

//get connection with redis
func GetConn() redis.Conn {
	conn, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", 6379))
	if err != nil {
		fmt.Println("redis err", err)
		return nil
	}
	return conn
}

//change the interface type into string type
func interfaceToString(x interface{}) string {
	xx, err1 := redis.Strings(x, nil)
	if err1 != nil {
		fmt.Println("interfaceToString err", err1)
		fmt.Println("xx=", xx, "xx[0]=", xx[0])
	}
	xxx := xx[0]
	return xxx
}

var times int //used to set ip address
//set ip address
func setIp() string {
	str := strconv.Itoa(times)
	times = times + 1
	return str
}

var rejoin_number int

//set ip address for users
func getIp(uid string) string {
	if IpList[uid] != "" {
		if Status[uid] != 3 {
			return IpList[uid]
		} else { //rejoin users
			rejoin_number++
			Status[uid] = 0
			x := setIp()
			IpList[uid] = x
			db1_key_channel <- uid
			db1_val_channel <- x
			return x
		}
	} else { //new users
		NameList = append(NameList, uid)
		Status[uid] = 0
		x := setIp()
		IpList[uid] = x
		db1_key_channel <- uid
		db1_val_channel <- x
		return x
	}
}

//db2, client queue;cache time and messages in clients
func pushIntoDB2(uid, time, msg string) {
	k1 := uid + "_msg"
	k2 := uid + "_time"
	db2_msg_key_channel <- k1
	db2_time_key_channel <- k2
	db2_msg_val_channel <- msg
	db2_time_val_channel <- time
}

//4 Variables used in itest2
var User_id_queue string
var Msg_queue string
var Timestamp_queue string
var User_Ip string

//used in db1,key is uid; val is ip
var db1_key_channel chan string
var db1_val_channel chan string
var mutex_db1 sync.Mutex

func db1_Set_Goroutine() {
	defer from_db0_input_wg.Done()
	conn := GetConn()
	conn.Do("select", 1)
	for {
		mutex_db1.Lock()
		x, ok1 := <-db1_key_channel
		y, ok2 := <-db1_val_channel
		mutex_db1.Unlock()
		if !ok1 || !ok2 {
			return
		}
		conn.Do("set", x, y)
	}
}

//db2's channels, used to store timestamp in db2
var db2_time_key_channel chan string
var db2_time_val_channel chan string
var mutex_db2_time sync.Mutex

func db2_Time_Set_Goroutine() {
	defer from_db0_input_wg.Done()
	conn := GetConn()
	conn.Do("select", 2)
	for {
		mutex_db2_time.Lock()
		x, ok1 := <-db2_time_key_channel
		y, ok2 := <-db2_time_val_channel
		mutex_db2_time.Unlock()
		if !ok1 || !ok2 {
			return
		}
		conn.Do("lpush", x, y)
	}
}

//db2's channel. used to store messages in db2
var db2_msg_key_channel chan string
var db2_msg_val_channel chan string
var mutex_db2_msg sync.Mutex

func db2_Msg_Set_Goroutine() {
	defer from_db0_input_wg.Done()
	conn := GetConn()
	conn.Do("select", 2)
	for {
		mutex_db2_msg.Lock()
		x, ok1 := <-db2_msg_key_channel
		y, ok2 := <-db2_msg_val_channel
		mutex_db2_msg.Unlock()
		if !ok1 || !ok2 {
			return
		}
		conn.Do("lpush", x, y)
	}
}

//test2 is used to get messages from sample data in each round
//get all messages in wach hour
func test2(finalTime string, lastTimesIndex int) (finalIndex int) {
	fmt.Println("text2() starts")
	//goroutine六个channel的初始化
	db1_key_channel = make(chan string, channelSize)
	db1_val_channel = make(chan string, channelSize)
	db2_time_key_channel = make(chan string, channelSize)
	db2_time_val_channel = make(chan string, channelSize)
	db2_msg_key_channel = make(chan string, channelSize)
	db2_msg_val_channel = make(chan string, channelSize)
	wantToSendHash = make(map[string]int)
	conn := GetConn()
	for i := lastTimesIndex; ; i++ {
		//Used to determine if a timeout has occurred
		time1, err1 := conn.Do("hmget", i, "Timestamp")
		if err1 != nil {
			fmt.Println("Timestamp hmget err,err1=", err1)
		}
		Timestamp_queue = interfaceToString(time1)
		//timeout
		if Timestamp_queue > finalTime {
			finalIndex = i
			fmt.Println("i", i)
			break
		} else {
			//no timeout
			u_id, err := conn.Do("hmget", i, "User_id")
			if err != nil {
				fmt.Println("u_id hmget err,err1=", err)
			}
			User_id_queue = interfaceToString(u_id)
			wantToSendHash[User_id_queue] = 1
			messages, err2 := conn.Do("hmget", i, "Hashtags")
			if err2 != nil {
				fmt.Println("message hmget err,err2=", err2)
			}
			Msg_queue = interfaceToString(messages)
			User_Ip = getIp(User_id_queue)
			pushIntoDB2(User_id_queue, Timestamp_queue, Msg_queue)
		}
	}
	fmt.Println("for loop ends")
	//until now, all messages in this round have been taken from sample data;

	for i := 0; i < 130; i++ {
		from_db0_input_wg.Add(3)
		go db1_Set_Goroutine()
		go db2_Time_Set_Goroutine()
		go db2_Msg_Set_Goroutine()
	}
	close(db1_key_channel)
	close(db1_val_channel)
	close(db2_time_key_channel)
	close(db2_time_val_channel)
	close(db2_msg_key_channel)
	close(db2_msg_val_channel)
	from_db0_input_wg.Wait()

	fmt.Println("text2() ends")
	return
}

//used to porcess these messages from taxt2()
var real_msg_channel chan string
var cover_msg_channel chan string

//used to merged messages
type megred struct {
	Megred_Timestamp []string
	Megred_Hashtags  []string
	Megred_User_Id   string
	Megred_Ip        string
	Cover_Msg        string //if it is a cover message
}

type users struct {
	Uid   string
	Cover int
	Delay int
	Queue []string
}

type group struct {
	Group_id   int
	Users_list []*users //
	Pattern    []int    //
}

var from_db0_input_wg sync.WaitGroup

//used in sendOut_real; put all uid in wantToSendHash into real_msg_channel
func putWantToSendHashIntoChannel(real_msg_channel chan<- string) {
	fmt.Println("len(wantToSendHash)", len(wantToSendHash))
	for i := range wantToSendHash {
		real_msg_channel <- i
	}
	fmt.Println("already pu all uid in wantToSendHash into channel")
}

//used in  sendOut_cov; all uid in status_learning should send message(cover/real); if they don't have real. then they must send cover message
func putStatus_learningIntoChannel(cover_msg_channel chan<- string) {
	fmt.Println("len(status_learning)", len(status_learning))
	for _, v := range status_learning {
		cover_msg_channel <- v
	}
	fmt.Println("already all uid in status_learning into channel")
}

//connect to agent
func getHttpConnection() net.Conn {
	httpConn, err := net.Dial("tcp", "192.168.32.144:20000")
	if err != nil {
		fmt.Println("http conn err,dial 192.168.32.144:20000 failed", err)
		return nil
	}
	return httpConn
}

//taken all message in db2, merge them into structure
func merge(v string, conn redis.Conn) *megred {
	conn.Do("select", "2")
	a := v + "_time"
	b := v + "_msg"
	l1, err_L1 := conn.Do("LLEN", a)
	if err_L1 != nil {
		fmt.Println("err_L1=", err_L1)
	}
	length1, err_length1 := redis.Int(l1, nil)
	if err_length1 != nil {
		fmt.Println("err_length1=", err_length1)
	}
	l2, err_L2 := conn.Do("LLEN", b)
	if err_L2 != nil {
		fmt.Println("err_L2=", err_L2)
	}
	length2, err_length2 := redis.Int(l2, nil)
	if err_length2 != nil {
		fmt.Println("err_length2=", err_length2)
	}
	time_middle, _ := conn.Do("lrange", a, 0, length1-1)
	x, err_x := redis.Strings(time_middle, nil)
	if err_x != nil {
		fmt.Println("err_x=", err_x)
	}
	aa := x
	msg_middle, _ := conn.Do("lrange", b, 0, length2-1)
	y, errr_y := redis.Strings(msg_middle, nil)
	if errr_y != nil {
		fmt.Println("errr_y=", errr_y)
	}
	bb := y
	//clean two queue
	for i := 0; i < length1; i++ {
		conn.Do("rpop", a)
	}
	for i := 0; i < length2; i++ {
		conn.Do("rpop", b)
	}
	//do merging
	ip_middle := IpList[v]
	merged_result := &megred{
		aa,
		bb,
		v,
		ip_middle,
		"no", //only merge real messages; so not a cover
	}
	return merged_result
}

var cov_lock sync.Mutex
var covNumber int
var real_lock sync.Mutex
var realNumber int

var arrival_msg_number int
var learning_msg_number int
var online_msg_number int
var learning_msg_number_wg sync.Mutex
var online_msg_number_wg sync.Mutex

var test_list []string

//send real messages out
func sendOut_real(list <-chan string, c net.Conn, cover *megred) {
	defer finalSend_wg_real.Done()
	time.Sleep(time.Millisecond * 100)
	conn := GetConn()
	conn.Do("select", "2")
	for {
		v, ok := <-list
		if !ok {
			return
		}
		status_v := Status[v]
		switch status_v {
		case 0:
			arrival_msg_number++
		case 1:
			learning_msg_number_wg.Lock()
			learning_msg_number++
			learning_msg_number_wg.Unlock()
		case 2:
			online_msg_number_wg.Lock()
			online_msg_number++
			test_list = append(test_list, v)
			online_msg_number_wg.Unlock()
		}
		rst := merge(v, conn) //get merged message
		jsons, err_json := json.Marshal(*rst)
		if err_json != nil {
			fmt.Println("err_json=", err_json)
		}
		//do padding
		total_length := packetLength
		padding_length := total_length - len(jsons)
		if jsons[len(jsons)-1] != 125 {
			fmt.Println("rst=", rst)
			break
		}
		//it is too long, we use a cover message for this too loooooong real message
		if padding_length <= 0 {
			cov_mid := *cover
			cov_mid.Megred_User_Id = v
			cov_mid.Cover_Msg = "no"
			cov_mid.Megred_Hashtags = []string{"[other]"}
			cov_mid.Megred_Ip = IpList[v]
			cov_mid.Megred_Timestamp = []string{"000000000"}
			jsons, err_json := json.Marshal(cov_mid)
			if err_json != nil {
				fmt.Println("err_json=", err_json)
			}
			total_length := packetLength
			padding_length := total_length - len(jsons)
			pd := make([]byte, padding_length)
			jsons = append(jsons, pd...)
			if len(jsons) != packetLength {
				fmt.Println("err,cover=", jsons)
			}
			c.Write(jsons)
			real_lock.Lock()
			realNumber++
			real_lock.Unlock()
			continue
		}
		pd := make([]byte, padding_length)
		jsons = append(jsons, pd...)
		if len(jsons) != packetLength {
			fmt.Println("err出错了，长度不对，rst=", rst)
		}
		c.Write(jsons)
		real_lock.Lock()
		realNumber++
		real_lock.Unlock()
	}
}

//used to send out cover messages
func sendOut_cover(list <-chan string, c net.Conn, cover *megred) {
	defer finalSend_wg_cov.Done()
	time.Sleep(time.Millisecond * 100)
	for {
		v, ok := <-list
		if !ok {
			break
		} else {
			if wantToSendHash[v] == 1 {
				continue //user in learning phase and has a real message in this round.then he don's need to send cover message
			} else {
				//should send cover message
				status_v := Status[v]
				switch status_v {
				case 1:
					learning_msg_number_wg.Lock()
					learning_msg_number++
					learning_msg_number_wg.Unlock()
				}

				cov_mid := *cover
				cov_mid.Megred_User_Id = v
				cov_mid.Cover_Msg = "yes"
				cov_mid.Megred_Hashtags = []string{"[cover]"}
				cov_mid.Megred_Ip = IpList[v]
				cov_mid.Megred_Timestamp = []string{"000000000"}
				jsons, err_json := json.Marshal(cov_mid)

				if err_json != nil {
					fmt.Println("err_json=", err_json)
				}
				total_length := packetLength
				padding_length := total_length - len(jsons)
				pd := make([]byte, padding_length)
				jsons = append(jsons, pd...)
				if len(jsons) != packetLength {
					fmt.Println("err，cover=", jsons)
				}
				c.Write(jsons)
				cov_lock.Lock()
				covNumber++
				cov_lock.Unlock()
			}
		}
	}
}

//tell agent, this round ends, it also needs to be padded
func roundEnd(c net.Conn) {
	fmt.Println("send roundEnd")
	x := []byte("roundEnd")
	total_length := packetLength
	padding_length := total_length - len(x)
	pd := make([]byte, padding_length)
	x = append(x, pd...)
	if len(x) != packetLength {
		fmt.Println("err，x=", x)
	}
	c.Write(x)
}

var finalSend_wg_cov sync.WaitGroup
var finalSend_wg_real sync.WaitGroup

//call all send actions
func finalSend() {
	c := getHttpConnection()
	cover := &megred{
		[]string{},
		[]string{},
		"",
		"cover",
		"yes",
	}
	real_msg_channel = make(chan string)
	cover_msg_channel = make(chan string)
	for i := 0; i < 1000; i++ {
		finalSend_wg_real.Add(1)
		finalSend_wg_cov.Add(1)
		go sendOut_real(real_msg_channel, c, cover)
		go sendOut_cover(cover_msg_channel, c, cover)
	}
	putWantToSendHashIntoChannel(real_msg_channel)
	putStatus_learningIntoChannel(cover_msg_channel)
	close(real_msg_channel)
	close(cover_msg_channel)
	finalSend_wg_real.Wait()
	finalSend_wg_cov.Wait()
	time.Sleep(time.Millisecond * 100)
	roundEnd(c)
}

//init db1,2
func initDB1_DB2() {
	conn := GetConn()
	conn.Do("select", "1")
	conn.Do("flushdb")
	conn.Do("keys *")
	conn.Do("select", "2")
	conn.Do("flushdb")
	conn.Do("keys *")
}

//some init works
func start() {
	initDB1_DB2()
	times = 0
	IpList = make(map[string]string)
	Status = make(map[string]int)
	NameList = make([]string, 0)
	status_learning = make([]string, 0)
	packetLength = 3000
}

//Calculate the time, because text2 takes messages within one hour each time, and needs this time as deadline
func timeAddOneHour(input string) (outpute string) {
	loc, err_loc := time.LoadLocation("Local")
	if err_loc != nil {
		fmt.Println("err_loc=", err_loc)
	}
	the_time, err_the_time := time.ParseInLocation("20060102150405", input, loc)
	if err_the_time != nil {
		fmt.Println("err_the_time=", err_the_time)
	}
	//1 hour; 2 hours; 30 min
	h, _ := time.ParseDuration("1h")
	h1 := the_time.Add(1 * h)
	//h, _ := time.ParseDuration("1m")
	//h1 := the_time.Add(30 * h)
	//h, _ := time.ParseDuration("1h")
	//h1 := the_time.Add(2 * h)
	timeString := h1.Format("20060102150405")
	outpute = timeString
	return
}

var learning_phase_start_round int
var round int

//reaceive the feedback from agent; this is for users from arrival to learning
func StatusChangeOrNot_arrival_to_learning() {
	defer log.Println("StatusChangeOrNot_arrival_to_learning() ends")
	fmt.Println("starts StatusChangeOrNot_arrival_to_learning()")
	listener, err := net.Listen("tcp", "192.168.32.144:20002")
	if err != nil {
		fmt.Println("StatusChangeOrNot_arrival_to_learning(),as server，listen 192.168.32.144:20002 fails", err)
		return
	}
	conn, err := listener.Accept()
	if err != nil {
		fmt.Println("accept failed,StatusChangeOrNot_arrival_to_learning conn err", err)
		return
	}
	var tmp [10000]byte
	res := ""
	log.Println("all users who have changed their status")
	for {
		n, err := conn.Read(tmp[:])
		if err != nil {
			fmt.Println("read from conn failed", err)
			break
		}
		//no change, directly return
		if string(tmp[:8]) == "noChange" {
			log.Println("StatusChangeOrNot_arrival_to_learning ends,no user changes status")
			listener.Close()
			return
		} else {
			result := string(tmp[:n])
			res = res + result
			if string(tmp[:9]) == "changeEnd" {
				fmt.Println("received all status")
				listener.Close()
				break
			}
		}
	}
	//space
	res = strings.Replace(res, " ", "", -1)
	//line
	res = strings.Replace(res, "\n", "", -1)
	str_arr := strings.Split(res, ",")
	learning_phase_start_round = round
	for _, str := range str_arr {
		Status[str] = 1
		status_learning = append(status_learning, str)
	}
	fmt.Println("StatusChangeOrNot_arrival_to_learning ends。someone has changed his status, len(status_learning)=", len(status_learning))
}

//feedback, users who have been eliminated in online phase
func StatusChangeOrNot_eliminated() {
	defer fmt.Println("StatusChangeOrNot_eliminated ends")
	log.Println("start StatusChangeOrNot_eliminated()")
	listener, err := net.Listen("tcp", "192.168.32.144:20002")
	if err != nil {
		fmt.Println("StatusChangeOrNot_eliminated(),as server，listen 192.168.32.144:20002 failed", err)
		return
	}
	conn, err := listener.Accept()
	if err != nil {
		fmt.Println("accept failed,StatusChangeOrNot_eliminated conn err", err)
		return
	}
	var tmp [10000]byte
	res := ""
	log.Println("eliminated uid")
	for {
		n, err := conn.Read(tmp[:])
		if err != nil {
			fmt.Println("read from conn failed", err)
			break
		}
		//no change
		if string(tmp[:8]) == "noChange" {
			fmt.Println("StatusChangeOrNot_eliminatedends,no change")
			listener.Close()
			return
		} else {
			result := string(tmp[:n])
			res = res + result
			if string(tmp[:9]) == "changeEnd" {
				fmt.Println("received all status")
				listener.Close()
				break
			}
		}
	}
	//space
	res = strings.Replace(res, " ", "", -1)
	//line
	res = strings.Replace(res, "\n", "", -1)
	str_arr := strings.Split(res, ",")
	//set status =3, which means eliminated
	for _, str := range str_arr {
		Status[str] = 3
	}
	log.Println("StatusChangeOrNot_eliminatedends,len(str_arr)=", len(str_arr))
	log.Println("the eliminated users are", str_arr)
}

//Learning phase's length,24/48/72 rounds
func refresh_status_learning() {
	if round-learning_phase_start_round == 24 { //24，48，72
		fmt.Println("start refresh_status_learning")
		fmt.Println("status_learning is refreshed，len（status_learning）=", len(status_learning))
		status_learning = make([]string, 0)
	}
}

//users from learning to online phase
func StatusChangeOrNot_learning_to_online() {
	defer log.Println("StatusChangeOrNot_learning_to_online() ends")
	log.Println("进入StatusChangeOrNot_learning_to_online() starts")
	listener, err := net.Listen("tcp", "192.168.32.144:20002")
	if err != nil {
		fmt.Println("StatusChangeOrNot_learning_to_online(),as server，listen 192.168.32.144:20002 failed", err)
		return
	}
	conn, err := listener.Accept()
	if err != nil {
		fmt.Println("accept failed", err)
		return
	}
	var tmp [10000]byte
	res := ""
	for {
		n, err := conn.Read(tmp[:])
		if err != nil {
			fmt.Println("read from conn failed", err)
			break
		}
		//no change
		if string(tmp[:8]) == "noChange" {
			log.Println("no change")
			listener.Close()
			return
		} else {
			result := string(tmp[:n])
			res = res + result
			if string(tmp[:9]) == "changeEnd" {
				fmt.Println("received all status")
				listener.Close()
				break
			}
		}
	}
	//space
	res = strings.Replace(res, " ", "", -1)
	//line
	res = strings.Replace(res, "\n", "", -1)
	str_arr := strings.Split(res, ",")
	//set status to 2,namely online
	number := 0
	for i := 0; i < len(str_arr)-1; i++ {
		Status[str_arr[i]] = 2
		number++
	}
	fmt.Println("from learning to online users' number=", number)
	fmt.Println("someone has changed into online phase")
}

func main() {
	//log
	logFile, err := os.OpenFile("log.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	if err != nil {
		panic(err)
	}
	defer logFile.Close()
	mw := io.MultiWriter(os.Stdout, logFile)
	log.SetOutput(mw)
	start()
	channelSize = 150000
	//1st test point,1101
	lastTimesIndex := 0
	roundEndTime := "20121101130000"
	//2ed test point,1110
	//lastTimesIndex := 1479975
	//roundEndTime := "20121110130000"
	//3rd test point,1120
	//lastTimesIndex := 3751916
	//roundEndTime := "20121120130000"

	for i := 0; i < 35; i++ {
		fmt.Println("------------------------------------------------------------the", i, "th round starts------------------------------------------------------------------")
		arrival_msg_number = 0
		learning_msg_number = 0
		online_msg_number = 0
		rejoin_number = 0
		round = i
		covNumber = 0
		realNumber = 0
		lastTimesIndex = test2(roundEndTime, lastTimesIndex)
		fmt.Println("lastTimesIndex=", lastTimesIndex)
		roundEndTime = timeAddOneHour(roundEndTime)
		fmt.Println("roundEndTime=", roundEndTime)
		fmt.Println("status len", len(Status), "NameListlen ", len(NameList), "status_learning len", len(status_learning))
		finalSend()
		refresh_status_learning()
		fmt.Println("Status len", len(Status))
		StatusChangeOrNot_arrival_to_learning()
		StatusChangeOrNot_learning_to_online()
		StatusChangeOrNot_eliminated()
		fmt.Println("covNumber=", covNumber)
		fmt.Println("realNumber=", realNumber)
		fmt.Println("arrival_msg_number=", arrival_msg_number, "learning_msg_number=", learning_msg_number, "online_msg_number=", online_msg_number)
		log.Println("rejoin_number", rejoin_number)
		fmt.Println("------------------------------------------------------------the", i, "th round ends------------------------------------------------------------------")
	}
}
